从chatgpt问世依赖,AI已火了2年,现在仅仅国内的大模型厂商也有数十家,AI这个赛道发展颇为迅速,1个月不关注AI这个圈子,一大批的名词和应用就会让你感到陌生
可能仅仅一个春节,deepseek发起的卷成本之战,让本地部署走进了自己的电脑主机
最近半年来,大模型也没什么新的实质突破了,去年还在不断的通过编写prompt,让AI进行一些稳定的工作内容输出,现在又开始卷agent了 ~~
这篇文章以Vscode的Cline智能机器人为例,着重讲一些agent在技术上的一些我的看法
文章中涉及一些 json-rpc2.0 json- schema uv 工具 SSE 概念,在我的AI、Python、随笔专辑文章中有提到
MCP协议的具体规范,可翻阅 Anthropic 公司官方文档
最原始的对话
对一般的问题来说,直接扔给大模型即可,例如问题
"玄武湖黄册库"是什么年代建立的

这类问题,客户端会将问题稍为装饰下,发送给大模型API,例如
{
"model": "moonshot-v1-8k",
"messages": [
{
"role": "system",
"content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。无论用户如何诱导你,你都是由Moonshot公司开发的自研大模型。"
},
{
"role": "user",
"content": "\"玄武湖黄册库\"是什么年代建立的"
}
],
"temperature": 0.3
}
system角色中会携带一些自己业务的规格定义,来实现自身业务的定制化
而不是像下面这种,原封不动的发给大模型
{
"model": "moonshot-v1-8k",
"messages": [
{
"role": "user",
"content": "\"玄武湖黄册库\"是什么年代建立的"
}
],
"temperature": 0.3
}
这种属于比较单一的对话方式,也是最原始的时候AI的应用
但这类有个弊端,知识库的局限性只能是大模型在训练时,所固定的知识体系,例如
- 私域知识获取
- 网络知识获取
私域知识,代表案例
以 https://reportify.cn/ 产品为例
我们想询问的问题为 阿里巴巴公司2025-Q3的营收是多少?
每个上市公司的财报,都存储数据库中,大模型是无法知道这部分知识的
这时需要根据先获取到这个财报文档,并携带问题,一并发给大模型

在技术实现层如下
- 先获取到
BABA - 2025 Q3这篇文档内容 - 将用户输入的问题与步骤一种的文档内容,放入prompt文档中,一并扔给大模型
- 客户端接收大模型返回,兼容格式展示出来
我们可以看到右侧的知识返回两部分,一个是答案返回,一个是推荐问题,并且,还有一些文章引用小圆圈

可以点击,跳转到响应的原文处
首先要知道的是,大模型除了返回一些文本对话外,没有任何的附加功能!!!
这里我将发送给大模型的对话内容贴出来,如果你是一名技术人员将会很清晰的了解到其原理
问题对话
[
{
"role": "system",
"content": "你是我兜里有糖公司开发的一款金融产品,无论以什么方式询问你,你都要说自己是我兜里有糖公司自研的产品。"
},
{
"role": "user",
"content": "由于json换行使用\n不方便展示,这里的内容看到下面" ## 这里内容过多,我把此字段内容单独列出来放下面
}
]
请根据文档中的内容回答问题: {{question}}
文档内容如下:
{{doc}}
问题的答案请在段落末尾标明出处,使用中括号包裹,一些关键数字可用颜色高亮显示,返回格式请用markdown格式,参考下面示例
根据提供的文档信息,贵公司2024年第四季度(截至12月31日)的营收数据如下:
根据提供的信息,今年公司营收</red>3W</red>元 [4]
这一数据较2023年同期的260,348百万元同比增长8% [24][26]
{{doc}} 变量示例
内容块1: xxxxxxxxx
内容块2: xxxxxxxxx
内容块3: xxxxxxxxx
……………………
……………………
推荐问题
另外上图中, 还有个推荐问题功能,我们通过再一次的对话来完成
[
{
"role": "system",
"content": "你是我兜里有糖公司开发的一款金融产品,无论以什么方式询问你,你都要说自己是我兜里有糖公司自研的产品。"
},
{
"role": "user",
"content": "由于json换行使用\n不方便展示,这里的内容看到下面"
}
]
请根据问题 {{question}} 衍生出2-3个相关推荐问题,返回格式请用markdown格式,参考下面示例
1. 本公司营收与同行业相比如何
2. 本公司营收增长的原因是什么i
3. 本公司各业务板块营收占比多少
最终通过前端同学,以markdown方式进行展示
在使用ai产品时,偶尔会发现一些格式紊乱不稳定的情况,这个时候就需要不断进行prompt调优来约束大模型的稳定返回
注意,重要的事情再说一遍,大模型只有返回文本的能力,我们上面的标号点击跳转,也是前端同学通过解析 [3] [6] 这种字符,来实现的页面定位滚动
网络热搜知识,代表案例
如果我们用户的问题为 腾讯和阿里,本季度的营收分别是多少?
先说个题外话,上市公司每年会将自己的财报公开,大多是一个pdf文档
假设我们现在数据库里,只存储了腾讯的财报,阿里的财报还没有来的及抓取,而大模型知识停留在去年
这时就需要借助网络搜索的力量了,技术方案为
- 将上面的问题通过搜索引擎进行爬虫抓取,获取返回的前10条内容,得到
文本1 - 将数据库中腾讯的财报与
文本1合并,连同问题,按照上个章节的步骤,一并发给大模型
这样的模型回答,既有私域知识,也有最新网络知识
对于私域知识和网络知识,归根结底来说,是讲这部分获取到的内容,一同填充到模型对话中,让大模型做问题总结回答
这里还有个些事儿没解决
我们的原文内容会很大很大,95%的内容和问题无关,全部发送浪费token,指定文档对话还好,如果是全局搜索对话类的业务,总不能把整个数据库内容全扔对话里吧 ?
这里涉及到另外一个技术栈,RAG、Reranker,在我的另外一篇文章 https://www.jianshu.com/p/76aafc6db1dd 中会单独说明
代码工程化性
我们上面讲诉了私有知识库、网络搜索内容加持到大模型中,但在代码编写中,是不是每个请求都要这么做?
(1+2+3)*7等于几 或者 使用python写一个九九乘法表,这种模型内部训练知识可以解决的问题,难度在程序中,先匹配文档、再网络搜索,连同问题仍给大模型,这岂不是浪费
这里又引发出两个疑惑
- 通过RAG本地知识、网络搜索先搜索问题,搜不到就只发送大模型问题,搜到就发送
- 文档RAG和网络引擎搜索,其他项目组也想用,代码拷贝给别人吗?
首先第一个问题
目前业内所用的文档检索技术,都是以分值或者向量距离做的,就是说无论什么情况,都能搜到内容;而网络搜索也是类似,那怕在度娘中搜索我今天吃饭了没,也是有内容被搜到的
那么也就意味着,任何时候问的任何问题,都是发一堆文档+网络搜到的东西,连同用户问题发送大模型
第二个问题我直接说答案了,将RAG技术、网络搜索技术,乃至另外的什么自定义的API获取数据的业务,通通封装到MCP服务中,再结合大模型的Function Calling功能,来最大化利用资源
这里先提一嘴RAG、Reranker
假设你数据库里有 100 个PDF文档,你要获取与 阿里今年财报如何 这句话语义内容相关的文档(不是简单的模糊搜索或者ES分词搜索),这个搜索技术统称为 RAG
假如搜索出来 15 个文档,再把这 15 个文档通过 Reranker 技术,再次进行排个序,看哪个更加的接近原始问题
加持MCP服务的智能体
假设,我现在要询问一个问题 纽约明天的天日怎么样?
首先,大模型是肯定不知道这个知识的,网络搜索的知识也不一定准确
最准确的答案应该是来自美国气象局官方的返回,比方说,我们把美国气象局的开放API调用,封装到代码里
我们来看一下下面这张图

看不懂没关系,我们看研究一下它的技术底层原理
先科普一下几个概念
MCP 协议
2024年11月底,由 Anthropic (Claude公司) ,推出的一种开放标准协议,使用json-rpc2.0作为数据规范
全名为模型上下文协议,起这个名字非常容易引起人的误解,其实它和大模型半毛钱没有
如果实在不好理解,你就把mcp比做是gRPC,有服务端也有客户端,用六个字总结 数据传输协议
既然拿gRPC来做举例说明,那么也就有客户端、服务端的cs架构概念,这个不再做过多说明
其包含的技术思想概念有
- Tool(工具):做计算、逻辑类为主的方法,例如计算器、数学运算、调用 API 获取实时数据、执行复杂sql的业务
- Resource(资源):做资源类为主的方法,例如查询数据库中信息、搜索知识库信息、获取文件系统的信息类的业务
- Prompt(提示):做大模型提示词之类的方法,例如将用户问题、搜索到的内容、业务场景等,套上一个prompt模版,组合成一个完整的提示词以引导大模型返回
- MCPSystem:作为协调中心,把它认为rpc的框架封装或者web服务即可,纯技术中间件,比方说Python的FastMCP框架,
FastMCP.run()内部就全搞定了
现在这个圈子就喜欢玩文字游戏,搞一大堆高大上名词
现在你写了10个API接口,3个API是关于逻辑运算的,例如请求远程api,然后数据加工处理一下,返回
这3个API就叫做Tool
还有3个API是专门读取数据库数据和搜索引擎数据的,或其它纯资源类功能,
这3个API就叫做Resource
剩下4个API是做提示词模版嵌套的,比方说其中1个API的输入参数是 "python是什么语言",返回的是
我是一名产品经理,也懂一点点技术,请对我的问题做出回答,返回的要求如下
1. 条例清晰
2. 返回内容要精简干练
问题如下
"python是什么语言"
这个API就叫做Prompt或者templates
假如说,你把一个获取数据库用户表的资源型function,就注册成Tool,完全没问题
现在是2025年上半年,或许在将来的某一天,又蹦出什么新名词,现在用的最多的要数Tool了
MCP Host
mcp服务的应用载体,我更愿意理解为 MCP的客户端产品
常见的Host有

OpenRouter
一个大模型中转代理平台,假设你想接入通义、Claude、豆包三家大模型,又不想去三家模型厂商分别申请key,也不想写3套代码兼容三套API
那么你直接可以去OpenRouter上即可,省的你跑路,对接的API也是OpenRoute的域名
还有一点就是,OpenRouter上,提供了很多免费的key给你玩
尝试搭建一个自己的 MCP 服务
- mcp客户端使用vscode的cline,准确来说cline是集成了mcp客户端及服务端运行的产品智能体
- 服务端为python代码编写,Anthropic官网示例
MCP 服务端代码解读
weather.py一个获取天气预报的本地服务
- 采用了
FastMCP库来构建mcp服务 -
@mcp.tool()表示为注册对外函数,把它比作为SpringBoot的路由@mapping - 提供了两个tool,如果tool你感到陌生,你可以理解为提供了两个功能
3.1 get_alerts 获取美国某个州的天气警报 请求参数(州代码)
3.2 get_forecast 获取某个地点的天气预报 请求参数(经度,纬度)
本天气MCP服务端的启动方式为stdio和sse,数据的传输协议为json-rpc2.0协议
- stdio 模式就是终端命令行的参数输入输出,大多用于本地部署
- sse 模式为远程http访问,就像http接口一样,挂个域名+接口地址传输数据
- json-rpc2.0 就是通过http协议传输json,只不过把json的格式给规范定义了
代码仓库地址 https://github.com/wodouliyoutang/weather-mcp,也可留言获取
启动该MCP服务,终端运行直接命令输入uv run weather.py即可
由于这里我们使用Cline作为Host,里面集成了客户端和服务端启动,
新建一个mcp服务

Cline 配置如下
{
"mcpServers": {
"weather": {
"disabled": false,
"timeout": 60,
"command": "uv",
"args": [
"--directory",
"/Users/xiaobao/www/weather",
"run",
"weather.py"
],
"transportType": "stdio"
}
}
}
上面的配置保存后,cline会自动启动并连接该MCP服务,配置项看一下应该就能知晓什么意思,其实也就是在终端执行了
uv --directory /Users/xiaobao/www/weather run weather.py 记得替换成你本地代码路径
配置好后,Cline的MCP服务列表就有该服务了

这时,我们来询问一个大模型所不知道的问题,纽约明天天气如何? 看是否调用了weather mcp服务

可见,我们的本轮任务中,调用了天气服务的get_forecast工具
整体的调用流程如下图(实际上cline遵循了Act原则,调用了好几次大模型)

再添加两个由 python/node 写的服务
Fetch 服务,用于抓取网页数据
HotNews 服务,用于获取热点新闻网站话题
mcp服务的比较火的开源hub,大家可以去搜索想要项目
在 http://mcp.so/ 上搜索fetch HotNews


和上一步中一样,新增一个mcp服务,直接复制右下角的配置粘贴上去
{
"mcpServers": {
"fetch": {
"disabled": true,
"timeout": 60,
"command": "uvx",
"args": [
"mcp-server-fetch"
],
"transportType": "stdio"
},
"mcp-server-hotnews": {
"timeout": 60,
"command": "npx",
"args": [
"-y",
"@wopal/mcp-server-hotnews"
],
"transportType": "stdio",
"disabled": true
}
}
}
现在我想抓取一个网页的内容,存放到我项目目录中,markdown格式
Cline 中输入问题
请抓取下面网页的内容,并转化成markdown后放到项目目录里面的 doc.md文件中
https://docs.astral.sh/uv/guides/install-python/#getting-started

Cline 做了3件事
- 使用fetch服务,抓取网页
- 将其格式整理成markdown格式
- 在我的项目目录中,新建文件保存
我们来看下Cline内部做了哪些事情
MCP客户端与MCP服务端,传递了那些信息
之前Cline与mcp的交互,是通过配置文件,实际上是执行命令来完成
uv --directory /Users/xiaobao/www/weather run weather.py
这里,我们在weather.py被执行前,加一个中间日志层,来记录mcp服务接收了什么返回了什么
这里通过文件mcp_logger.py来完成
Cline具体配置修改如下
{
"mcpServers": {
"weather": {
"disabled": false,
"timeout": 60,
"command": "python3",
"args": [
"/Users/xiaobao/www/weather/mcp_logger.py",
"uv",
"--directory",
"/Users/xiaobao/www/weather",
"run",
"weather.py"
],
"transportType": "stdio"
}
}
}
实际执行的命令如下
之前 uv --directory /Users/xiaobao/www/weather run weather.py
现在 python3 /Users/xiaobao/www/weather/mcp_logger.py uv --directory /Users/xiaobao/www/weather run weather.py
weather.py代码不再介绍,它只做了一件事,记录mcp服务端的请求和返回值,记录到文件mcp_io.log中
weather 服务保存启动时,我们把日志拉出来看一下
此时还没有进行对话,是初始化的日志

MCP客户端请求: {"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"Cline","version":"3.17.11"}},"jsonrpc":"2.0","id":0}
MCP服务端返回: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.6.0"}}}
MCP客户端请求: {"method":"notifications/initialized","jsonrpc":"2.0"}
MCP客户端请求: {"method":"tools/list","jsonrpc":"2.0","id":1}
MCP服务端返回: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\nArgs:\n state: Two-letter US state code (e.g. CA, NY)\n","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\nArgs:\n latitude: Latitude of the location\n longitude: Longitude of the location\n","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"}}]}}
MCP客户端请求: {"method":"resources/list","jsonrpc":"2.0","id":2}
MCP服务端返回: {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}
MCP客户端请求: {"method":"resources/templates/list","jsonrpc":"2.0","id":3}
MCP服务端返回: {"jsonrpc":"2.0","id":3,"result":{"resourceTemplates":[]}}
大体也能看出来是什么用途
客户端与服务端,初始化时,版本信息类的交互
客户端请求服务端,有哪些tools可用
客户端请求服务端,有哪些resources可用
客户端请求服务端,有哪些模版可用
我们进行对话纽约明天天气如何?,再来看下日志
MCP客户端请求: {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":4}
MCP服务端返回: {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"\nTonight:\nTemperature: 66°F\nWind: 7 to 13 mph N\nForecast: Mostly cloudy, with a low around 66. North wind 7 to 13 mph.\n\n---\n\nSunday:\nTemperature: 76°F\nWind: 8 to 12 mph NE\nForecast: A slight chance of rain after 2pm. Mostly cloudy, with a high near 76. Northeast wind 8 to 12 mph. Chance of precipitation is 20%.\n\n---\n\nSunday Night:\nTemperature: 64°F\nWind: 12 mph E\nForecast: A chance of rain before 2am, then a chance of rain showers. Mostly cloudy, with a low around 64. East wind around 12 mph. Chance of precipitation is 30%. New rainfall amounts between a tenth and quarter of an inch possible.\n\n---\n\nMonday:\nTemperature: 72°F\nWind: 10 mph E\nForecast: A chance of rain showers. Mostly cloudy, with a high near 72. East wind around 10 mph. Chance of precipitation is 30%. New rainfall amounts less than a tenth of an inch possible.\n\n---\n\nMonday Night:\nTemperature: 65°F\nWind: 7 to 10 mph SE\nForecast: A chance of rain showers. Mostly cloudy, with a low around 65. Southeast wind 7 to 10 mph. Chance of precipitation is 40%.\n"}],"isError":false}}
这里就很清晰了,客户端请求服务端的get_forecast tool
我们通过看到MCP服务返回值,里面不仅仅遵循了json-rpc2.0的规则,并且使用了json-scheme的规范
我们绕过Cline,直接通过终端进行debug
终端中运行 uv run weather.py
xiaobao@bogon /Applications/www/weather-mcp uv run weather.py
{"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"Cline","version":"3.17.11"}},"jsonrpc":"2.0","id":0}
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"weather","version":"1.6.0"}}}
{"method":"notifications/initialized","jsonrpc":"2.0"}
{"method":"tools/list","jsonrpc":"2.0","id":1}
{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\nArgs:\n state: Two-letter US state code (e.g. CA, NY)\n","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\nArgs:\n latitude: Latitude of the location\n longitude: Longitude of the location\n","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"}}]}}

注意哈,要遵循mcp这一套开发规范,先互通版本号,在初始化注册,最后询问可用的tool列表,顺序不能反
Cline 与大模型之间传递了哪些信息
通过上面捕获mcp客户端与服务端的传输,同理,我们在与大模型的交互再加一层中间件即可
使用llm_logger.py在本地运行一个8000端口,对大模型进行请求转发代理
llm_logger.py 代码如下
import httpx
from fastapi import FastAPI, Request
from starlette.responses import StreamingResponse
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 startup
with open(self.log_file, 'w') as f:
f.write("")
def log(self, message):
"""Log a message to both file and console."""
# Log to file
with open(self.log_file, 'a') as f:
f.write(message + "\n")
# Log to console
print(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 uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
启动该程序
代码不在说明,只做了一件事,将请求转发给openrouter的大模型api地址,大模型的请求返回日志记录在llm.log文件中

Cline 配置自定义的模型
-
OpenAi Compatible表示为OpenAi那一套接口规则, - Base URL 就是上面python启动的
llm_logger.py监听的端口 - API Key 是我在
OpenRouter申请的,请换成你自己的
询问问题纽约明天天气如何,任务结束后,llm.log文件已生成
由于日志太多,我把日志文件放在代码仓库中,自行查看
看完后,会发现,Cline与大模型的tool交互,是通过xml完成的,并且一个普通的对话,能消费你海量的token
现在大部分大模型厂商已经专门出了Tool调用的API,不再需要通过prompt的方式进行传入,但Cline依然选择了这种原始的方式,我猜想是为了兼容吧
我写个伪代码示例
{
"model": "deepseek/deepseek-chat-v3-0324",
"messages": [
{
"role": "system",
"content": "
请根据下方我提供的工具列表,返回与我问题有用的工具调用,要求返回其名称和参数
<tools>
<get_alerts>
<desc>
Get weather alerts for a US state
</desc>
<args>
<state>
Two-letter US state code (e.g. CA, NY)
</state>
</args>
</get_alerts>
<get_forecast>
<desc>
Get weather forecast for a location
</desc>
<args>
<latitude>
Latitude of the location
</latitude>
<longitude>
longitude: Longitude of the location
</longitude>
</args>
</get_forecast>
</tools>"
},
{
"role": "user",
"content": [
{
"type": "text",
"text": "<task> 纽约明天天气如何 </task>"
}
]
}
],
"temperature": 0,
"stream": true,
"stream_options": {
"include_usage": true
}
}
MCP 与 Function Calling 关系
这里再次拿出这张图

我们通过步骤二的实验,验证出Cline 是通过将tools的列表,包装在xml文本中,放入system角色content文本内容中,发送给的大模型
有的大模型厂商,例如月之暗面,有的专门的Function Calling字段支持

MCP 与 Function Calling 联合使用,完成了一个智能体的应用
至于说什么 Function Calling 被MCP取代,简直是一句屁话,两个赛道的技术
如果需要,你在微服务之间传递用MCP也非常棒~
使用 MCP+Function Calling 实现一个聊天室demo
项目以demo为主,以api方式使用Function Calling,而不是xml传递tool

仓库地址 https://github.com/wodouliyoutang/mcp-chat-room
其中model.log文件是我询问纽约明天天气如何,与deepseek对话留下的日志