我抓包了 Cline 与模型的通信,发现了一件有趣的事

0 阅读5分钟

MCP 规定了工具怎么注册和调用,但没规定工具信息怎么传给 LLM。Cline 是怎么做的?通过搭建一个中间人服务器抓包,完整的通信协议暴露在眼前。


从一个问题开始

学完 MCP 基础之后,你可能会有一个疑问:

"MCP 定义了 Host 和 Server 之间的通信,但是 Host 怎么把工具信息传递给 LLM?"

官方文档语焉不详。不同的 Host 实现方式不一样,Cline 怎么做的,只有一种方法能知道——抓包


搭建中间人服务器

思路很简单:在 Cline 和真实 LLM API 之间插入一个"代理"服务器,记录所有流量。

正常流程:
Cline → OpenRouter/Claude API

抓包流程:
Cline → 本地代理服务器(记录流量)→ OpenRouter/Claude API

实现代理服务器(FastAPI + httpx)

# proxy.py
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
import json
import time

app = FastAPI()

TARGET_API = "https://openrouter.ai/api/v1"
LOG_FILE = "llm.log"

@app.post("/v1/chat/completions")
async def proxy_chat(request: Request):
    body = await request.json()
    headers = dict(request.headers)
    
    # 记录请求
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(f"\n{'='*60}\n")
        f.write(f"[{time.strftime('%H:%M:%S')}] REQUEST\n")
        f.write(json.dumps(body, ensure_ascii=False, indent=2))
        f.write("\n")
    
    # 转发给真实 API
    async def generate():
        async with httpx.AsyncClient() as client:
            async with client.stream(
                "POST",
                f"{TARGET_API}/chat/completions",
                json=body,
                headers={
                    "Authorization": f"Bearer {YOUR_API_KEY}",
                    "Content-Type": "application/json"
                },
                timeout=120.0
            ) as response:
                full_response = ""
                async for chunk in response.aiter_text():
                    full_response += chunk
                    yield chunk
                
                # 记录响应
                with open(LOG_FILE, "a", encoding="utf-8") as f:
                    f.write(f"[{time.strftime('%H:%M:%S')}] RESPONSE\n")
                    f.write(full_response[:5000])  # 只记录前 5000 字符
                    f.write("\n")
    
    return StreamingResponse(generate(), media_type="text/event-stream")

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

配置 Cline 使用代理

在 Cline 的 API 设置中:

  • Provider:OpenAI Compatible
  • Base URL:http://localhost:8000
  • API Key:任意字符串(代理会替换)

抓包结果:Cline 发送了什么?

启动代理,在 Cline 里输入一个需要调用 MCP 工具的任务:"帮我查一下纽约今天的天气"

日志文件里出现了一段令人震惊的内容:

System Prompt 长达数万字符

Cline 发送给 LLM 的 System Prompt,不是你想象中简单的几句话,而是一个数万字符的庞然大物,包含:

  1. 角色定义:你是一个高度熟练的软件工程师...
  2. 工具调用格式(XML 标签规范)
  3. 所有内置工具的说明(read_file、write_to_file、execute_command...)
  4. 所有 MCP 工具的说明(来自已配置的 MCP Server)
  5. 详细的行为规范(何时询问用户、如何处理错误...)
<!-- System Prompt 片段(极度简化) -->
你是 Cline,一位高度熟练的软件工程师,能够使用各种工具完成各种任务。

## 工具使用格式
你可以通过 XML 标签格式使用工具:

<tool_name>
<parameter_name>parameter_value</parameter_name>
</tool_name>

## 可用工具

### read_file
读取文件内容
<read_file>
<path>目标文件路径</path>
</read_file>

### use_mcp_tool
使用 MCP 工具
<use_mcp_tool>
<server_name>服务器名</server_name>
<tool_name>工具名</tool_name>
<arguments>{"参数": "值"}</arguments>
</use_mcp_tool>

## 已配置的 MCP 服务器
### weather(天气查询服务)
- get_forecast:获取指定经纬度的天气预报
  参数:latitude(纬度)、longitude(经度)

用户消息被包装成 XML

<task>帮我查一下纽约今天的天气</task>
<environment_details>
当前时间:2026-03-16 21:00:00
操作系统:macOS 14.0
打开的文件:无
</environment_details>

LLM 的响应也是 XML 格式

<thinking>
用户想知道纽约的天气。我需要使用 get_forecast 工具。
纽约的坐标大约是纬度 40.71,经度 -74.01。
</thinking>

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

Cline 解析到 <use_mcp_tool> 标签,执行工具调用,把结果返回给 LLM:

<tool_result>
纽约今天晴,气温 18°C,偏北风 2 级,紫外线指数中等。
</tool_result>

LLM 收到工具结果后,输出最终答案,并用 <attempt_completion> 标记任务完成:

<attempt_completion>
<result>
纽约今天天气晴好,气温 18°C,偏北风 2 级,适合出行。紫外线指数中等,建议出门涂防晒。
</result>
</attempt_completion>

这揭示了什么?

核心发现一:Cline 用 XML 而非 Function Calling

OpenAI 定义了标准的 Function Calling 格式(JSON Schema),但 Cline 没有用——它自己发明了一套 XML 格式。

这说明:MCP 协议不规定 Host 如何与 LLM 交互,每个 Host 可以自行设计。

CherryStudio 用 Function Calling,Cline 用 XML——这是两种完全不同的实现,但都能工作,因为 MCP 只管 Host 和 Server 之间的通信。

核心发现二:ReAct 模式的工程化实现

Cline 的整个交互流程,就是 ReAct(Reasoning + Acting) 模式的工程落地:

Thought(思考)→ Action(行动)→ Observation(观察)→ 重复

通过 <thinking> 标签强制模型先思考再行动,通过 XML 结构化输出解析工具调用,通过工具结果反馈推动下一轮推理。

核心发现三:System Prompt 是 Agent 的真正配置文件

Cline 的 System Prompt 动辄数万 Token,占整个 Context 的相当大一部分。这个巨大的 System Prompt,包含了 Agent 的所有"规则"和"能力清单"。

这也解释了为什么:

  • Cline 的启动成本高(每次对话都要发送这个巨大的 System Prompt)
  • Cline 的 Agent 能力强(工具使用规范非常详细,模型很少用错)

能学到什么?

抓包分析的实践价值:

1. 调试工具调用失败:当 LLM 不调用工具,或者调用参数错误,看日志能快速定位是 Tool 的 description 写得不够好,还是 LLM 理解错了。

2. 自定义 Agent 开发参考:Cline 的 XML 格式是一种可以借鉴的设计思路。如果你要开发自己的 Agent,可以参考这种"先思考、再行动"的 Prompt 结构。

3. 成本控制:看到 System Prompt 多大,你就知道每次对话"基础成本"是多少,可以合理规划 Context 预算。


动手试一试

代码仓库里有完整的中间人代理代码:

git clone https://github.com/MarkTechStation/VideoCode
cd VideoCode/MCP终极指南-番外篇
pip install fastapi uvicorn httpx
python proxy.py

配置完 Cline,随便问一个需要工具的问题,然后打开 llm.log——你会看到比任何文档都更真实的 MCP 工作原理。