MCP 的定义
MCP(Model Context Protocol),即模型上下文协议,是由 Anthropic 公司于 2024 年 11 月推出并开源的一种通信协议。它旨在为大型语言模型(LLM)与外部数据源及工具之间的集成提供标准化的接口,从而实现无缝的交互体验。
MCP 与 function call 的关系
MCP 与 function call都是拓展大模型的能力,第一次接触MCP的时候,第一反应就是这个跟function call有什么区别,如何还不知道function call的看我另外一篇文章如何用Function Calling解锁OpenAI的「真实世界」交互能力?(附Node.js 实战)
,经深入了解,其实MCP最终也是以function call的形式给到大模型,本质还是function call。那有function call为啥还要整出个MCP。写过
function call的人都知道,这个一般都是把代码逻辑和函数跟大模型的调用代码写一起,不对外开放,别人或其他项目也用不了,为了让这些
扩展的能力能让所有的大模型都能无缝对接,就需要设计一套大家都通用的协议,就是MCP协议,大家都按这套协议开发出来的服务器,我们叫他
MCP服务器,任何的大模型,通过MCP客户端就可以无缝添加任何的MCP服务器来扩展大模型的能力。
MCP 的组成部分及工作原理
完整的一套MCP需要服务端,客户端,和大模型3部分组成,MCP的客户端负责跟MCP服务器通讯,并把执行结果给回大模型
第一步:MCP客户端先跟MCP服务器获取tools,就是给到大模型的function call定义,通知大模型MCP服务器有哪些工具能用
第二步:当大模型识别到输入需要使用到某个个工具的时候,就会通知到MCP客户端,MCP客户端再调用MCP服务端对应的函数,并将结果给回大模型,大模型根据返回结果和上下文,给出答案。
nodejs实现一个MCP服务器和客户端的例子
假设我们想给大模型扩展个客户管理能力,让用户跟大模型对话的时候,可以添加客户,删除客户,查询客户,那么MCP服务器可以这么做。
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "crm",
version: "1.0.0"
});
server.tool("addCustom",
"添加客户",
{
name: z.string().nonempty().describe("客户名字"),
phone: z.string().nonempty().describe("客户电话或手机号码"),
},
async ({ name, phone }) => ({
content: [{ type: "text", text: "客户:" + name + " 电话:" + phone + " 已添加" }],
})
);
server.tool("delCustom",
"删除客户",
{
name: z.string().nonempty().describe("客户名字"),
},
async ({ name }) => ({
content: [{ type: "text", text: "客户:" + name + " 已删除" }],
})
);
server.tool("getCustom",
"查询客户",
{
name: z.string().nonempty().describe("客户名字"),
},
async ({ name }) => ({
content: [{ type: "text", text: "客户:" + name + " 电话:12345678901" }],
})
);
// Start receiving messages on stdin and sending messages on stdout
const transport = new StdioServerTransport();
await server.connect(transport);
这里补充一下MCP服务器跟MCP客户端有2种通讯方式
1.StdioClientTransport 是通过本地命令行标准输入输出的方式通讯,这种需要本地具备运行服务端程序的环境才行。(上述的例子就是这种通讯方式),客户端的配置类似:
{
command: "node",
args: ["server.js"]
}
2.SSEServerTransport 是通过http请求的方式通讯,这种对客户端的环境要求就比较低,通过配置url即可,客户端的配置类似:
url:"http://localhost:3001/sse"
回到上述例子的客户端这么写
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import {
convertMcpToLangchainTools
} from "@h1deya/langchain-mcp-tools";
const mcpServers = {
crm: {
command: "node",
args: ["s.mjs"],
},
};
const { tools, cleanup } = await convertMcpToLangchainTools(mcpServers);
const llm = new ChatOpenAI({
model: "qwen-turbo",
configuration: {
apiKey: "",
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
});
const agent = createReactAgent({
llm,
tools
});
const output = await agent.streamEvents(
{
messages: [
{
role: "user",
// content: "记录下客户,名字是张三,电话是123456789",
content: "删除客户小龙,查询张三,李四的电话号码",
}
],
}, {version: "v2"}
);
console.log("\x1b[36m"); // color to cyan
for await (const { event, data } of output) {
if (event === "on_chat_model_stream") {
const msg = data.chunk;
if (msg.content) {
console.log(msg.content);
}
}
}
console.log("\x1b[0m"); // reset the color
执行效果
可以看到,当我们跟大模型说“删除客户小龙,查询张三,李四的电话号码”,之后,大模型就主动找我们给大模型的删除客户接口,查询客户接口,并且会同时使用。
MCP的客户端也可以脱离大模型,单独运行
,这在开发MCP服务端,测试就很有用,可以直接通过MCP客户端调用服务端直接对接口进行测试。
例如:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
const transport = new StdioClientTransport({
command: "node",
args: ["s.mjs"]
});
const url='http://localhost:3001/sse';
const transport2 = new SSEClientTransport(new URL(url));;
const client = new Client(
{
name: "example-client",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
const client2 = new Client(
{
name: "example-client1",
version: "1.0.0"
},
{
capabilities: {
prompts: {},
resources: {},
tools: {}
}
}
);
console.log(1)
await client.connect(transport);
await client2.connect(transport2);
console.log(2)
// List prompts
//const prompts = await client.listPrompts();
// Get a prompt
/*
const prompt = await client.getPrompt("example-prompt", {
arg1: "value"
});*/
// List resources
//const resources = await client.listResources();
// Read a resource
//const resource = await client.readResource("file:///example.txt");
// Call a tool
const result = await client.callTool({
name: "add",
arguments: {
a: 1,
b:2
}
});
console.log(result)
const result2 = await client2.callTool({
name: "add2",
arguments: {
a: 1,
b:2
}
});
console.log(result2)
总结
MCP可以认为是对function call做了一个解耦封装,让拓展可以在另外一个单独项目随时新增,而且可以提供给任意按MCP协议实现的客户端大模型使用。