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,不是你想象中简单的几句话,而是一个数万字符的庞然大物,包含:
- 角色定义:你是一个高度熟练的软件工程师...
- 工具调用格式(XML 标签规范)
- 所有内置工具的说明(read_file、write_to_file、execute_command...)
- 所有 MCP 工具的说明(来自已配置的 MCP Server)
- 详细的行为规范(何时询问用户、如何处理错误...)
<!-- 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 工作原理。