MCP(Model Context Protocol)入门与实践:让 AI Agent 跨进程调用工具
一、问题的起点:Tool 的复用困境
在构建 AI Agent 时,我们经常需要给大模型绑定各种工具(Tool)。比如一个查询用户信息的工具:
const queryUserTool = {
name: 'query_user',
description: '查询用户信息',
// ...
}
但这样写存在两个明显的问题:
- 只能在这个项目中使用,不好复用——工具代码和 Agent 代码耦合在一起,换个项目就得重写。
- 语言绑定问题——如果工具是用 Node.js 写的,那 Java、Python、Rust 的项目怎么用?
核心诉求就是:让 Tool 独立于 LLM,实现本地/远程、跨进程、跨语言的调用。
二、MCP 协议是什么
MCP(Model Context Protocol) 是一种标准化的协议,用于规范 LLM 与 Tool、Resource 之间的通信,核心目标是 解耦 LLM 和 Tool。
通信方式
MCP 支持两种通信模式:
| 模式 | 传输方式 | 适用场景 |
|---|---|---|
| stdio | 标准输入输出流 | 本地跨进程调用 |
| HTTP | 远程 HTTP 通信 | 远程跨进程调用 |
本质上,MCP 就是让 Agent 能够跨进程调用工具——不管是本地进程还是远程进程,通过 MCP 协议就能搞定。
用一句话概括:MCP 是给 Model 扩展 Context(上下文),让它能做的更多(Tool)、知道的更多(Resource)的协议。
与普通 API 调用的区别
MCP 和 fetch 调用接口不一样——它不是去拿接口数据,而是要扩展 Context(工具能力 + 资源知识),让 LLM 在推理时拥有更丰富的上下文。
三、MCP 的核心概念
3.1 Tool(工具)
本质就是工具,和普通的 Function Calling / Tool Use 没有本质差别,区别在于它是跨进程提供的。Agent(MCP Client / Host)通过协议去发现和调用远程进程中的工具——就像"抛饵"出去,让别的进程来执行。
3.2 Resource(资源)
Resource 是 MCP 另一大亮点。它允许 MCP Server 提供静态资源(文档、指南等),这些资源可以作为 System Prompt 的一部分注入到 Context 中。
看一个实际的 Resource 注册例子(来自 my-mcp-server.mjs):
server.registerResource(
'使用指南',
'docs://guide', // URI 格式的访问路径
{
description: '使用指南',
mimeType: 'text/plain'
},
async () => {
return {
contents: [{
uri: 'docs://guide',
mimeType: 'text/plain',
text: `
MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具。
`
}]
}
}
)
在 Client 端读取 Resource 的方式:
const res = await mcpClient.listResources();
let resourceContent = '';
for (const [serverName, resources] of Object.entries(res)) {
for (const resource of resources) {
const content = await mcpClient.readResource(serverName, resource.uri);
resourceContent += content[0].text;
}
}
// 将 resource 内容作为 SystemMessage 注入
const messages = [
new SystemMessage(resourceContent), // 资源内容成为上下文的一部分
new HumanMessage(query),
];
Resource vs RAG:Resource 是 RAG 之外另一种丰富上下文的手段。相比 RAG 需要先检索,Resource 更适合提供固定的、结构化的参考文档。当然,受限于上下文窗口大小,不是所有文档都适合直接塞进去。
四、实战:搭建一个完整的 MCP 调用链路
下面通过实际代码展示一个完整的 MCP 工作流程。架构如下:
┌──────────────┐ stdio ┌─────────────────┐
│ MCP Client │ ◄──────────► │ MCP Server │
│ (LangChain) │ 跨进程通信 │ (Node.js 进程) │
│ │ │ │
│ - 获取工具 │ │ - query_user │
│ - 获取资源 │ │ - docs://guide │
│ - Agent 循环 │ │ │
└──────────────┘ └─────────────────┘
4.1 MCP Server 端
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// 模拟数据库
const database = {
users: {
'001': { id: '001', name: '祖豪', email: 'zh@qq.com', role: 'admin' },
'002': { id: '002', name: '光光', email: 'gg@qq.com', role: 'user' },
'003': { id: '003', name: '小红', email: 'xh@qq.com', role: 'user' },
}
}
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
// 注册工具
server.registerTool('query_user', {
description: '查询数据库中的用户信息',
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} 不存在` }]
}
}
return {
content: [{
type: 'text',
text: `用户 ${user.id} 的信息是:姓名:${user.name}, 邮箱:${user.email}, 角色:${user.role}`
}]
}
});
// 注册资源
server.registerResource('使用指南', 'docs://guide', { /* ... */ }, async () => { /* ... */ });
// 通过 stdio 启动通信
const transport = new StdioServerTransport();
await server.connect(transport);
关键点:
- 用
McpServer创建服务实例 - 通过
registerTool注册工具,使用 Zod 定义参数 Schema - 通过
registerResource注册静态资源 - 使用
StdioServerTransport建立 stdio 通信通道
4.2 MCP Client 端
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
import { ChatOpenAI } from '@langchain/openai';
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
// ...配置
});
// 配置 MCP Client,可以同时连接多个 MCP Server
const mcpClient = new MultiServerMCPClient({
mcpServers: {
'my-mcp-server': {
command: 'node', // 启动命令
args: ['src/my-mcp-server.mjs'], // 脚本路径
cwd: '/path/to/mcp-demo' // 工作目录
}
}
});
// 获取工具和资源
const tools = await mcpClient.getTools();
// 获取资源并拼成上下文字符串
const res = await mcpClient.listResources();
// ...拼接 resourceContent ...
// 绑定工具到模型
const modelWithTools = model.bindTools(tools);
关键点:
MultiServerMCPClient可以同时配置多个 MCP Server- 每个 Server 配置指定启动命令、参数和工作目录
- Client 内部通过
child_process启动子进程,通过 stdio 通信
4.3 Agent 工具调用循环
async function runAgentWithTools(query, maxIterations = 30) {
const messages = [
new SystemMessage(resourceContent), // 资源内容作为系统提示
new HumanMessage(query),
];
for (let i = 0; i < maxIterations; i++) {
const response = await modelWithTools.invoke(messages);
messages.push(response);
// 没有工具调用 → 直接返回最终回复
if (!response.tool_calls || response.tool_calls.length === 0) {
return response.content;
}
// 执行每个工具调用
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);
messages.push(new ToolMessage({
content: toolResult,
tool_call_id: toolCall.id, // ⚠️ 必须带上 tool_call_id
}));
}
}
}
return messages[messages.length - 1].content;
}
这个循环有几个值得注意的细节:
- SystemMessage 注入 Resource:把 MCP Server 提供的文档资源作为系统提示,让模型"知道得更多"
- 最大迭代次数:设置 30 轮上限防止无限循环
tool_call_id必须回传:ToolMessage 必须带上对应的tool_call_id,这是模型关联工具调用和结果的唯一标识tools.find()匹配:find方法找到第一个匹配项就停止,适合工具名唯一的场景
4.4 资源清理
// 关闭所有 MCP 子进程与通信通道,释放进程资源
await mcpClient.close();
这一步很重要! close() 会:
- 关闭与 MCP Server 的 stdio 连接
- 终止
child_process启动的子进程 - 避免脚本挂起不退出
五、关键技术细节
5.1 跨进程通信(IPC)的本质
主进程 (Agent/LangChain)
│
│ child_process.spawn('node', ['my-mcp-server.mjs'])
│
├── stdin ──► MCP Server 子进程
│ (接收工具调用请求)
│
◄── stdout ── MCP Server 子进程
(返回工具执行结果)
- 父子进程模型:Client 通过
child_process启动 Server 作为子进程 - stdio 通信:请求通过 stdin 发送,响应通过 stdout 返回
- 多语言支持:任何语言的进程只要实现了 MCP 协议的 stdio 通信,就能被调用
5.2 JavaScript 的异步特性
整个通信链路中,JavaScript 的单线程异步无阻塞模型保证了:
- 主线程不会被子进程通信阻塞
- 多个 MCP Server 可以并发通信
- Agent 循环中的每轮推理和工具调用都是异步的
5.3 Object.entries 的妙用
在处理 MCP 返回的资源列表时,Object.entries() 非常实用:
// MCP 返回的是按 Server 分组的资源对象
const res = {
'my-mcp-server': [
{ uri: 'docs://guide', name: '使用指南' },
{ uri: 'docs://api', name: 'API文档' },
]
};
// Object.entries 拆解为 [key, value] 遍历
for (const [serverName, resources] of Object.entries(res)) {
for (const resource of resources) {
// 逐个读取资源内容
}
}
六、总结
| 维度 | 传统 Tool | MCP Tool |
|---|---|---|
| 复用性 | 绑定在项目中 | 独立进程,任意项目可用 |
| 跨语言 | 仅限同语言 | 通过 stdio/http,任意语言 |
| 通信方式 | 同进程函数调用 | 跨进程(本地 stdio / 远程 HTTP) |
| 扩展性 | 手动添加 | 配置式添加 MCP Server |
| 上下文丰富 | 仅 Tool | Tool + Resource + Prompt |
MCP 协议的核心价值在于:
- 解耦:Tool 和 LLM 彻底分离,各自独立开发、部署、迭代
- 跨进程:不管是本地子进程还是远程服务,统一通过 MCP 协议通信
- 跨语言:Node.js 写的工具可以被 Python Agent 调用,反之亦然
- 标准化:统一的 Tool 定义、Resource 提供、通信方式,生态可共享
MCP 让 AI Agent 从一个"孤岛"变成了可以接入各种能力(Tool)和知识(Resource)的"开放平台"。这就是它被称为 AI 的 USB-C 接口的原因。