前言
做 AI Agent 开发长期有两个致命痛点:
- 工具代码和主项目强耦合,换一套 Agent 就要复制、重写适配层,完全无法复用;
- 工具受开发语言限制,Node 写的工具,Java/Python/Rust 程序不能直接调用,跨语言通信成本极高。
传统两种方案都有短板:
- 工具内嵌主进程:耦合严重、复用性差;
- HTTP 接口封装工具:只是单纯拿数据,无法原生融入 LLM 上下文体系。
Model Context Protocol(MCP,模型上下文协议) 专门解决以上问题:一套标准化通信协议,让 Agent 以跨进程方式调用任意语言、独立部署的工具与静态资源,实现 LLM、工具、上下文彻底解耦。
本文分模块拆解服务端、客户端逻辑,不堆砌大段代码,逐段讲清原理与作用。
一、MCP 基础核心概念
1. MCP 是什么
一套标准化通信规范,负责打通 LLM 客户端(Agent)和外部能力服务端,统一交互格式。 服务端对外提供两类核心能力:
- Tool:可执行工具函数,LLM 按需主动调用;
- Resource:静态文本资源,提前注入模型上下文。
2. 两种通信传输层
- Stdio(本地) 利用操作系统标准输入输出流做 IPC 父子进程通信,无需端口,轻量无额外开销,本地工具首选。
- HTTP(远程) 跨机器网络调用,适合分布式、远程部署的 MCP 服务。
3. 核心优势:跨进程解耦
传统工具:和 Agent 跑在同一个进程,依赖、生命周期、代码全部绑定。 MCP 架构拆分两端,完全隔离:
- MCP Server:独立可执行程序,Node/Python/Java/Rust 都能实现,只暴露 Tool/Resource;
- MCP Client(Agent 宿主):LLM 主程序,可同时连接多个 MCP 服务,统一调用所有外部能力。
4. 和其他技术区分
- 对比 HTTP 接口 HTTP 仅返回原始数据;MCP 把工具、文档资源纳入 LLM 原生上下文,模型自动识别、自主决定调用时机。
- 对比 RAG RAG 擅长长文档检索;MCP Resource 是轻量化固定短文本(使用手册、规则),直接塞进系统提示词,二者互补使用。
二、MCP Server 服务端拆解(独立工具进程)
文件:my-mcp-server.mjs 作用:单独运行的子进程,对外暴露查询用户工具 + 使用文档资源,和 Agent 主程序无任何耦合。
1. 依赖导入
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
- McpServer:MCP 服务端核心实例,用于注册工具、资源;
- StdioServerTransport:stdio 标准流通信载体,实现跨进程 IPC;
- zod:参数校验库,标准化工具入参结构,给 LLM 清晰参数描述。
2. 模拟业务数据源
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' },
}
}
业务逻辑与 MCP 协议层分离,后续可无缝替换真实数据库,不改动 MCP 注册代码。
3. 初始化 MCP 服务实例
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
定义服务唯一标识、版本,客户端多服务场景区分不同工具服务。
4. 注册 Tool 工具:query_user
server.registerTool('query_user', {
description: `查询数据库中的用户信息。输入用户ID, 返回该用户的详细信息(姓名、邮箱、角色)`,
inputSchema: {
userId: z.string().describe('用户ID, 例如:001, 002, 003')
}
}, async ({userId}) => {
// 业务查询逻辑
});
三层结构拆解:
- 工具名:
query_user,客户端识别调用的唯一标识; - 元数据:description 给 LLM 看工具用途,inputSchema 定义入参格式、说明;
- 回调函数:接收 LLM 传入参数,执行业务逻辑,统一返回
{content: [{type: "text"}]}标准 MCP 格式文本。
内部业务逻辑:
- 根据 userId 匹配用户数据;
- 不存在则返回提示文本,告知可用 ID;
- 存在则拼接用户信息文本返回给客户端。
5. 注册 Resource 静态资源
server.registerResource(
'使用指南',
'docs://guide',
{ description: 'MCP Server 使用指南', mimeType: 'text/plain' },
async () => { return { contents: [{uri, text}] } }
)
参数说明:
- 资源展示名称;
- 资源唯一 URI 标识,客户端通过 URI 读取资源;
- 资源描述、文本类型;
- 回调:返回固定静态文本(工具使用手册、业务规范)。
作用:客户端启动时一次性读取全部 Resource,拼接成 SystemMessage 注入模型,相当于提前给 AI 下发业务规则。
6. 绑定 stdio 跨进程通信通道
const transport = new StdioServerTransport();
await server.connect(transport);
关键代码:把 MCP 服务挂载到标准输入输出流。 客户端通过 node 命令拉起本文件作为子进程,父子进程依靠 stdio 双向收发 MCP 协议消息,完成跨进程通信。
三、MCP Client Agent 客户端拆解(LLM 主程序)
文件:langchain-mcp-test.mjs 作用:AI 主程序,作为 MCP 客户端,自动拉起 MCP 子进程、读取资源、绑定跨进程工具、自动循环调用工具回答用户问题。
1. 依赖导入
import 'dotenv/config';
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
import { ChatOpenAI } from '@langchain/openai';
import chalk from 'chalk';
import { HumanMessage, SystemMessage, ToolMessage } from '@langchain/core/messages';
- dotenv:读取环境变量存储大模型密钥;
- MultiServerMCPClient:多 MCP 服务统一管理客户端,支持同时连接多个不同语言的 MCP 服务;
- ChatOpenAI:兼容 OpenAI 接口的大模型封装(DeepSeek / 通义千问均可);
- chalk:控制台彩色日志,方便调试工具调用流程;
- 消息类:LangChain 标准消息结构,区分系统提示、用户提问、工具返回结果。
2. 初始化大模型
const model = new ChatOpenAI({
modelName:'deepseek-v4-pro',
apiKey: process.env.DEEPSEEK_API_KEY,
temperature: 0,
configuration: { baseURL: 'https://api.deepseek.com/v1' },
});
temperature=0 关闭随机性,保证工具调用稳定;通过兼容接口对接国产大模型。
3. 配置 MCP 多服务客户端
const mcpClient = new MultiServerMCPClient({
mcpServers: {
'my-mcp-server': {
command: 'node',
args: ['文件绝对路径/my-mcp-server.mjs']
}
}
})
核心逻辑:
- 配置字典,key 为服务名称;
- command:启动服务端程序的命令(node/python/java/rust 二进制均可);
- args:启动参数,填入 MCP 服务端文件路径; 客户端内部自动调用 child_process 生成子进程,自动建立 stdio 通信,无需手动管理进程。 支持同时配置多个不同语言、不同路径的 MCP 服务。
4. 拉取工具 & 读取全部 Resource 资源
4.1 获取所有跨进程工具
const tools = await mcpClient.getTools();
客户端主动向所有 MCP 服务拉取工具元数据,自动封装成 LangChain 标准 Tool 实例,可直接绑定到大模型。
4.2 遍历读取所有静态资源,拼接系统提示词
const allResources = await mcpClient.listResources();
let resourceContent = '';
for (const [serverName, resources] of Object.entries(allResources)) {
for (const resource of resources) {
const contentList = await mcpClient.readResource(serverName, resource.uri)
resourceContent += contentList[0].text;
}
}
流程拆解:
- listResources:查询所有 MCP 服务注册的资源列表;
- 双层循环遍历每个服务、每个资源;
- readResource:根据服务名 + URI 读取资源文本;
- 全部文本拼接为一段完整上下文,存入 SystemMessage 给模型。
5. 模型绑定全部跨进程工具
const modelWithTools = model.bindTools(tools);
LangChain 标准化绑定,模型自动感知所有 MCP 暴露的跨进程工具,输出标准 tool_calls 调用结构。
6. Agent 自动工具循环函数 runAgentWithTools
入参与初始化消息
async function runAgentWithTools(query, maxIterations=30) {
const messages = [
new SystemMessage(resourceContent),
new HumanMessage(query)
];
- maxIterations:最大工具调用轮次,防止无限循环死循环;
- messages 消息队列:SystemMessage 存放 Resource 手册,HumanMessage 存放用户原始提问。
循环推理逻辑
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) {
console.log(`AI 最终回复:${response.content}`);
return response.content;
}
模型无 tool_calls,说明不需要再调用工具,直接输出自然语言回答,终止循环。
执行跨进程工具调用,回填结果
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
}))
}
}
执行流程:
- 遍历模型输出的每一条工具调用指令;
- 在 MCP 拉取的工具列表匹配对应工具;
- invoke 内部自动通过 stdio 把参数传给 MCP 子进程执行;
- 拿到服务端返回文本,封装 ToolMessage,携带 tool_call_id 存入消息队列;
- 进入下一轮循环,模型结合工具结果重新推理。
循环上限兜底
循环达到 30 次仍未给出最终回答,直接返回最后一轮模型输出,避免卡死。
7. 执行测试 & 释放进程资源
// 读取Resource测试
await runAgentWithTools('MCP Server的使用指南是什么?');
// 查询用户工具测试
// await runAgentWithTools('查一下用户002的信息');
// 关键:关闭所有MCP子进程
await mcpClient.close();
close () 作用:销毁所有自动拉起的 MCP 子进程、断开 stdio 通信通道,防止脚本执行完成后后台残留进程,程序正常退出。
四、完整运行链路(Stdio 本地跨进程)
- 执行客户端脚本,MultiServerMCPClient 通过 child_process 生成 MCP 服务端子进程;
- 父子进程绑定标准输入输出,建立 MCP 协议双向通信通道;
- 客户端主动拉取服务端全部 Tool、Resource 元数据;
- 读取所有 Resource 拼接为 SystemMessage,作为模型初始上下文;
- 用户提问送入消息队列,大模型判断是否调用工具;
- 工具调用参数通过 stdio 下发到子进程 MCP Server 执行业务逻辑;
- 工具结果原路返回主进程,封装 ToolMessage 追加到消息列表;
- 模型结合工具返回内容二次推理,无工具调用则输出最终回答;
- 任务结束调用 close,销毁全部子进程,程序退出。
五、MCP 如何解决开篇两大痛点
痛点 1:工具绑定项目,无法跨项目复用
MCP Server 是独立可执行程序,和 Agent 业务完全隔离:
- 单独打包部署,任意 Agent 项目仅需在 Client 配置中添加服务启动命令即可复用;
- 多套 Agent、多个业务系统可共用同一套 MCP 工具服务,无需复制工具代码。
痛点 2:仅 Node 可用,无法兼容 Java/Python/Rust 工具
MCP 是纯协议、语言无关:
- 传输层仅依赖 stdio 或 HTTP,任意语言实现 MCP SDK 即可作为 Server;
- 客户端统一封装,上层 Agent 完全无感知底层工具开发语言,调用逻辑不变。
六、拓展落地优化方向
- 远程部署改造 当前示例为本地 stdio 进程,分布式场景替换 HTTP 传输层,MCP 服务部署云端远程调用;
- Resource 与 RAG 搭配 Resource 适合短规范、工具手册;长业务知识库使用 RAG 检索,二者组合构建完整上下文;
- 多服务模块化拆分 MultiServerMCPClient 支持同时挂载数据库、文件处理、接口请求等多个独立 MCP 服务,工具按业务拆分;
- 生产环境增强 增加进程保活、调用超时、日志收集、权限校验,防止子进程崩溃阻塞 Agent。