在上一次的学习手写mini cursor中,我们给大模型提供tools的方式是利用langchain的tool方法自己在本地手写tools函数,其实这是非常不优雅的方式,这样的tools工具缺乏复用性,接下来,我将用全新的方式让大模型调用tools。
在过去的很长一段时间里,我们在开发 AI 应用或智能体(Agent)时,常常会遇到一个令人头疼的痛点:如何优雅地让大语言模型(LLM)使用外部工具?
以前,每一个 AI 应用都需要为不同的模型单独编写复杂的工具调用适配层。我们要处理各种各样的接口、不同的数据格式,如果想要集成跨语言的工具或者第三方的服务,对接联调的复杂性往往会呈指数级上升。
这不禁让人思考:难道就没有一个统一的标准吗?
直到 Anthropic 在 24 年底发布了 Model Context Protocol(MCP) 协议,并在 25 年底将其贡献给开源社区。MCP 的出现,彻底改变了 LLM 与外部世界交互的范式。
一、 什么是 MCP?AI 界的"USB-C"
MCP,本质上就是一种开放协议。它的核心目的,是为大模型与外部工具、数据源、系统环境之间的交互建立统一的标准。
我们可以把 MCP 形象地比作大模型的「USB-C 接口标准」。就像 USB-C 统一了各种电子设备的连接方式一样,MCP 试图统一模型如何读取上下文、调用工具、访问本地资源和外部系统。
现在,只要大模型和工具都支持 MCP,它们就可以按照这个标准协议进行无缝通信。这意味着什么?这意味着未来的软件生态可能会发生巨变。甚至可以说,80% 的 App 可能会消失,它们将化身为第三方的 MCP 服务,本质上变成了一个个随时待命的 Tool。大型互联网公司也会倾向于将自己的服务以 MCP 的方式向外提供。
MCP 的三大核心角色
要理解 MCP 是如何运转的,我们需要认识它的三个核心组件:
- MCP Host(宿主): 这是承载 AI Agent 或 LLM 会话,并负责工具编排的运行环境。比如我们常用的 Cursor 编程软件,或者基于 LangChain 开发的程序,它们就是 Host。Host 只需要知道如何使用 MCP 标准插头,就能驱动各种异构的工具。
- MCP Client(客户端): 这是遵循 MCP 协议的"工具接口实现"层。你可以把它理解为"插座的转接头"。它负责将 Host 的请求翻译并传递给后端的 Server。
- MCP Server(服务端): 这是实际执行工具逻辑的进程或服务,也就是真正的执行层。
强大的跨进程与通信能力
MCP 最显著的特点之一就是支持跨进程调用工具。
- 它可以调用本地的子进程(例如 Node.js 的
child_process)。 - 它能够实现跨语言的调用,比如 Node.js 去调用 Java、Python 或 Rust 编写的工具。
- 它甚至支持远程进程的调用。
在通信协议方面,MCP 支持两种主要方式:
- stdio: 用于本地命令行调用。
- http: 用于远程网络调用。
这种设计让繁杂的本地、跨语言、跨部门、远程协作变得井然有序,让 LLM 能够去执行更加庞大和复杂的任务。
二、 动手实践:编写你的第一个 MCP Server
理论说完了,让我们直接进入代码实战。我们要编写一个满足 MCP 协议规范的 Tool。
首先,你需要安装官方提供的 SDK,在 Node.js 中执行 pnpm i @modelcontextprotocol/sdk,这个库可用于实现 MCP Server 或 Client。
下面是一个名为 my-mcp-server.mjs 的本地 MCP 服务端代码示例。它的功能是模拟一个数据库查询工具。
JavaScript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// 1. 模拟一个简单的用户数据库
const database = {
users: {
"001": { id: "001", name: "张三", email: "zhangsan@example.com", role: "admin" },
"002": { id: "002", name: "李四", email: "lisi@example.com", role: "user" },
"003": { id: "003", name: "王五", email: "wangwu@example.com", role: "user" },
}
}
// 2. 初始化一个 MCP 服务器,命名为 my-mcp-server,版本 1.0.0
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
// 3. 注册核心功能:工具(Tool)
server.registerTool('query-user', {
description: '查询数据库中的用户信息,输入用户ID,返回该用户的详细信息(姓名、邮箱、角色)。',
inputSchema: {
userId: z.string().describe('用户ID,例如:001, 002, 003')
}
}, async ({ userId }) => {
const user = database.users[userId];
if (!user) {
return {
content: [{ type: 'text', text: `用户ID ${userId} 不存在。可用的ID: 001, 002, 003` }]
}
} else {
return {
content: [{
type: 'text',
text: `用户ID ${userId} 的信息如下:\n姓名 ${user.name},\n邮箱 ${user.email},\n角色 ${user.role}`
}]
}
}
})
// 4. 注册资源(Resource):提供给 LLM 的上下文知识
server.registerResource('使用指南', 'docs://guide', {
description: 'MCP Server 使用文档',
mimeType: 'text/plain',
}, async () => {
return {
contents: [{
uri: 'docs://guide',
mimeType: 'text/plain',
text: `MCP Server 使用指南\n功能:提供用户查询等工具。\n使用: 在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具`
}]
}
})
// 5. 启动服务器并连接标准输入输出传输层
const transport = new StdioServerTransport();
await server.connect(transport);
代码解析:
McpServer实例: 开发流程的第一步是new McpServer创建服务实例。- 注册工具与资源: 使用
server.registerTool和server.registerResource注册名称、描述和处理函数。注意,MCP 的上下文(Context)包含了 Tool、Resource 以及 PromptTemplate。Resource 使用 URI 作为统一资源定位符来进行唯一标识。 - 通信通道: 最后,我们创建了一个基于标准输入输出(stdio)的传输通道
StdioServerTransport,调用server.connect(transport)开始监听来自客户端的 stdin 请求,并通过 stdout 输出。
三、 组装大脑:在 LangChain 中集成 MCP Client
Server 准备好了,接下来我们需要一个"大脑"(Host)来调用它。这里我们使用 LangChain 来搭建一个具备自主调度能力的 Agent。
这是 langchain-host.mjs 的核心实现,它展示了 MCP 是如何即插即用的:
JavaScript
import 'dotenv/config';
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, ToolMessage } from '@langchain/core/messages';
import chalk from 'chalk';
// 1. 初始化 LLM 模型 (大模型的配置)
const model = new ChatOpenAI({
modelName: process.env.OPENAI_API_MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_API_BASE_URL }
});
// 2. 初始化 MCP Client (充当适配器)
const mcpClient = new MultiServerMCPClient({
mcpServers: {
'my-mcp-server': {
command: 'node',
// 这里填写刚才编写的 Server 代码的绝对路径
args: ['D:/workspace/lesson_zp/ai/agent/mini-cursor/mcp/mcp-tool/my-mcp-server.mjs']
},
}
});
// 获取所有工具,并绑定到大模型上
const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);
// 3. Agent 核心运行循环
async function runAgentWithTools(query, maxIterations = 30) {
const messages = [new HumanMessage(query)];
for (let i = 0; i < maxIterations; i++) {
console.log(chalk.bgGreen('⏳正在等待AI思考...'));
const response = await modelWithTools.invoke(messages);
messages.push(response); // 保存模型回复
// 如果模型没有决定调用工具,说明任务完成,直接返回结果
if (!response.tool_calls || response.tool_calls.length === 0) {
console.log(`\n AI 最终回复:\n ${response.content}\n`);
return response.content;
}
console.log(chalk.bgBlue(`🔧 检测到 ${response.tool_calls.length} 个工具调用: ${response.tool_calls.map(t => t.name).join(',')}`));
// 4. 执行模型请求的工具调用
for (const toolCall of response.tool_calls) {
const foundTool = tools.find(t => t.name === toolCall.name);
if (foundTool) {
const toolResult = await foundTool.invoke(toolCall.args);
// 将工具调用的结果封装为 ToolMessage 塞回上下文
messages.push(new ToolMessage({
content: toolResult,
too_call_id: toolCall.id
}));
}
}
}
return messages[messages.length - 1].content;
}
// 运行测试
const result = await runAgentWithTools('查一下用户 002 的信息');
await mcpClient.close();
端到端的工作流是怎样的?
结合上面的代码,我们可以清晰地看到一次标准的 MCP 调用流程:
- 发现: Host(我们的 Node.js 脚本)启动时,读取配置并通过 MCP Client 发现了我们编写的
query-user工具的名称、参数 schema 等信息。 - 调用: 用户输入查询需求,LLM 思考后认为需要调用该工具。Host 向 Client 发起调用请求。
- 转发与执行: Client 校验参数后将请求转发给我们的
my-mcp-server进程。Server 访问内存数据库,查到了"李四"的信息。 - 回传: Server 将结果回传给 Client,再回传给 Host,最终附加到上下文中反馈给 LLM 生成最终的人类可读文本。
四、 进阶实战:高阶组合,玩转多源 MCP
MCP 最震撼的威力在于组合。当你拥有了统一的标准,你就可以像搭积木一样,将各种复杂的第三方服务整合到一个 Agent 中。
在下面这个高阶示例中,我们同时接入了三个不同维度的 MCP Server:
JavaScript
// ... 引入模块省略 ...
const mcpClient = new MultiServerMCPClient({
mcpServers: {
// 1. 远程 HTTP MCP 服务:高德地图
"amap-maps-streamableHTTP": {
"url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`
},
// 2. 官方提供的本地文件系统 MCP 服务(Stdio)
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:/workspace/lesson_zp/ai/agent/mcp_in_action/mcp-test"
]
},
// 3. 浏览器自动化 MCP 服务(Stdio)
"chrome-devtools": {
"command": "npx",
"args": [
"-y",
"chrome-devtools-mcp@latest",
"--isolated"
]
}
}
})
// ... Agent 循环逻辑与之前类似,增加了完善的异常捕获与序列化处理 ...
// 震撼的组合调度任务:
await runAgentWithTools(`北京南站附近的3个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片,每个tab一个url展示,并且把那个页面标题改为酒店名`);
await mcpClient.close();
在这个例子中,Agent 被赋予了三个截然不同的能力:
- 远程 API 交互: 通过高德地图的 HTTP MCP 服务获取真实世界的位置和路线信息。
- 本地系统控制: 通过 npx 调用的
server-filesystem可以直接在本地硬盘读写 Markdown 文档。 - 浏览器控制: 利用
chrome-devtools-mcp直接操控浏览器,打开特定的 URL 修改标题。
当我们抛出 北京南站附近的3个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片... 这样一个极其复杂的复合型任务时,得益于 MCP 的标准化,大模型能够准确无误地规划并逐个调用这些异构工具,最终完成自动化操作流。
五、 总结
MCP 绝不仅仅是一个简单的通信库,它是通向真正的通用智能体(General-purpose Agent)的一块关键拼图。它极大地降低了开发者对接各种工具的复杂度,让我们可以将精力从"写适配器"转移到"设计业务逻辑与核心 Prompt"上。
未来的 AI 时代,得标准者得天下。掌握 MCP,就是掌握了让你的 AI 模型连接万物的钥匙。