1 mcp介绍
MCP(model context protocol)是最近半年,在AI领域比较火的一个概念了,它是给大模型提供上下文的一种协议。
什么是上下文呢?上下文本质就是传给大模型的prompt,LLM中很多技术概念都是围绕上下文出现的。用户自己输入问题就是上下文的主要部分。但是有时候用户的输入不够,这时候我们可能需要借助RAG和知识库来丰富上下文,知识库是用户自己准备的大量数据,通过可检索的方式在调用LLM前,搜索出相关的内容。再然后知识库还是不够,例如我们想要查询互联网,最新天气等,离线的知识库是肯定没有相关数据的,所以需要调用外部工具,所以有了function calling,这些都是来丰富上下文的。
那么MCP又做了什么呢?很多地方说MCP就是把function calling给规范化标准化了,有的地方也是MCP就是替换function calling的。这些说法都不太准确。MCP是把提供上下文的协议给规范化了,不仅对函数调用的过程,也对RAG的过程产生影响。
那么RAG和Function calling的现有流程中有哪些“不规范”的地方呢?
1.1 RAG与MCP resource
在RAG流程如下,我们需要自己完成R检索,A增强上下文的步骤,尤其是检索的这一步,我们需要自己在不同的知识库中,进行查询,可能用的是不同的数据库存储,不同的查询逻辑等。所以如果有多个知识库,这里就需要自己写一些定制化的逻辑。当然系统变复杂之后,我们可以把不同知识库的RAG封装成接口,入参是用户输入,出参是RAG增强后的prompt,这样就可以实现解耦了。但是至于这个接口怎么封装,传输方式,序列化方式等等,都是没有规范的,各个公司可以有自己的实现。

而MCP就是这样一个规范,它规定接口的出入参采用jsonrpc2.0格式规范,并且规定了传输协议有stdio和httpstream2种形式,参考,MCP就是在原有的链路调用中,在ai应用和RAG数据库查询之间加了一层适配层,这样ai应用就可以不用关心数据库调用部分的细节,如下图:

MCP server通过配置的方式注册到ai应用如cherry studio中,配置文件类似这样:
{
"mcpServers": {
"github.com/modelcontextprotocol/servers/tree/main/src/github": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "xxx"
},
"disabled": false,
"autoApprove": []
},
"demo": {
"command": "node",
"args": [
"C:\\Users\\sunwu\\Desktop\\code\\mcp-node\\src\\a1.mjs"
],
"disabled": false,
"autoApprove": []
}
}
}
主要就是配置了command+args的启动指令,在配置第一次保存后,ai应用通过标准的接口去初始化,查询mcpserver中有哪些可用的资源,如下图是claude dev这个工具接入MCP server的截图,可以看到每个接入的mcp中的tools和resources,后者就是对应的静态资源数据的检索,其实对应的是RAG或者知识库的概念,即静态的、只读的、供查询的数据的接口,resources类型。

在知识库查询过程中,流程中插入了MCP server这个中间层

1.2 function calling与MCP tool
tool和resource只是类型不同,流程上都需要对MCP server进行调用,所以和前面流程一致。MCP tool算是对function calling流程的增强
在原来的function calling流程中,用户把函数列表和问题一起发给LLM,后者根据上下文决定要调用某个函数后,将函数名和入参返回,再由Ai app调用具体的函数,最后这一步同样没有形成规范,如何调用,怎么调用等,不同的应用和插件都有自定义的形式,所以MCP server同样是规范化了这一步。下图是原来function calling流程

引入MCP后在函数调用者里套一层MCP server来标准化AI app的代码,但这其实还不够,因为function calling是部分模型支持的功能,有些模型可能不支持,为了让所有模型都能支持MCP tool,所以这里大多数支持MCP client的模型都采用定制化System prompt的形式。这里我们用cherry studio这个支持MCP的工具为例,先创建一个demo的mcp server它有个add函数运行加法,至于MCP server怎么写我们后面再说,这里我们先看流程。

然后诱导大模型用demo-add运行加法

抓包看到第一个请求,就返回了,将使用add来运算

这是因为System prompt中有列出可以使用的MCP server,我们之前也提到过claude dev也是类似的方式来让不支持function calling的模型,能够调用工具函数的。

返回的结果中有prompt中预设好的xml格式的函数调用描述,包括了函数名,函数参数:

此时cherry studio会识别xml,调用这个mcp server,调用返回3,然后将调用结果也给大模型再传过去,这是第二次LLM调用:

此时大模型应该总结下这个调用结果直接返回就好了,不过我这次调用的时候,大模型又尝试再次调用了add函数,所以这里有两次调用。所以我这个例子中调用了两次add,但是问题不大。
1.3 小结
上面例子中可以看出MCP其实没有提供功能和内容上的本质变化,只是在索要更多上下文的RAG function calling等流程中加了一层中间层。但也带来了很多好处,让RAG和tool的接入更加规范化了。
比如之前在coze中接入插件,要符合coze的插件标准,如果后续agent想切到别的平台,还需要对插件进行修改。但是如果各个平台都支持MCP标准的话,ai app的切换,就不影响插件的切换了。
2 MCP server
MCP server主要有三种形式,一种是位于本机(ai app所在的机器),执行的内容也是本机,例如本机的文件操作;第二种是位于本机,但是执行内容是远程调用,例如github的mcp server可以查询github热点,就是远程调用的gh的API;第三种是mcp server本身就运行在远程服务器。

其中需要低延迟和操作系统或本地软件权限的,都是第一种,例如:文件操作、控制浏览器、控制桌面等;需要远程服务接口支持的则是第二种为主,例如刚才提到的github的mcp server,它就是在本机运行一个代理,来接受ai app的请求,通过转换后发送到remote endpoint;最后如果想要灵活的控制mcp server的升级、收集用户数据等,也会采用第三种方案,用单独的服务器运行mcp server,不过这种比较少。
2.1 写一个
$ mkdir mcp-node && cd mcp-node
$ npm init -y
$ npm i @modelcontextprotocol/sdk
$ vim index.mjs
把官方ts库的代码贴到index.mjs里,虽然官方说是ts-sdk,但是没有用ts语法,直接在js中也能运行
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "demo-server",
version: "1.0.0"
});
// Add an addition tool
server.registerTool("add",
{
title: "Addition Tool",
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() }
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.registerResource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
{
title: "Greeting Resource", // Display name for UI
description: "Dynamic greeting generator"
},
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
server.connect(transport);
直接运行node a1.mjs,窗口会阻塞没有任何反应,这是因为当前使用的是stdio交互模式的StdioServerTransport,需要将jsonrpc2.0的request通过stdio的形式传入进来,才会有反应,像下面这样:

但是这个控制台echo的方式实在是太麻烦了,所以为了方便调试可以使用npm i -g @modelcontextprotocol/inspector,然后运行mcp-inspector,打开给的带token的url。

打开后如下填写,主要是运行我们的index.mjs文件,这个inspector会与我们运行起来的进行的stdio进行交互。

连接本质是initialize的过程,此时我们可以看到如下页面,通过list xx按钮可以展示有哪些资源和工具。

这里我们在tool中展示下所有的函数,然后点击add,输入入参,点击run,得到了最后的结果,这就是测试了。

我们会看下,server的代码new McpServer是创建了McpServer,registerTool和registerResource则是分别注册了一个tool和一个resource,前者实现了加法,后者会返回打招呼。最后通过StdioServerTransport这个最简单的stdio交互的方式来启动server,注意只有本机可以用这个,如果是远程的话,需要用httpstream的形式。
3 MCP client
client的作用就是来调用server,要根据stdio httpstream具体是那种传输形式和server进行匹配的交互。此外在ai app中要支持可配置mcpserver,所以有些工具例如chatbox等是不支持mcp的,而另外一些如cherry studio是支持的,这是因为后者集成了MCP client的功能,能对MCP server进行调用。
client也有对应的sdk,这里我就不展示了,因为自己暂时没有写MCP client的需求,一般是自己写ai app或者平台才需要自己写client。