你是否遇到过这种情况:辛辛苦苦给Agent写了一个本地工具,换个项目、换种语言就得重写?MCP(Model Context Protocol)就是为了解决这个问题而生的。本文将带你从零实现一个MCP Server,并用LangChain调用它,让你彻底理解MCP如何让工具跨语言、跨进程“即插即用”。
一、从“自嗨工具”到“通用能力”:我们为什么需要MCP?
先灵魂拷问一下:你平时给AI Agent写的工具(Tool)有什么痛点?
- 绑定特定项目:工具只能在一个代码库中用,换个项目就得复制粘贴。
- 语言限制:你用Node.js写了工具,但团队可能用Python、Java,想复用?得重写。
- 进程隔离:工具作为函数直接集成在Agent进程里,一崩溃全完蛋,而且无法分布式部署。
说白了,工具和LLM耦合太紧,变成了一次性的“自嗨玩具”。
如果有一种方式,让工具独立于LLM,无论本地还是远程,无论什么语言,Agent都能像插USB一样即插即用,那该多好?
MCP(Model Context Protocol) 就是来填补这个空白的。
二、MCP是什么?一句话说清核心价值
MCP = 标准化 LLM 与工具/资源通信的协议,让跨进程、跨语言的工具调用成为家常便饭。
MCP定义了Agent(作为客户端)与工具提供者(作为服务端)之间的通信规范:
- 通信方式:支持 stdio(本地跨进程,基于标准输入输出)和 HTTP(远程调用)。
- 暴露内容:Tool(可执行工具)、Resource(只读上下文数据)、Prompt(复用模板)。
- 核心作用:给Model扩展Context,让LLM不仅能“说”,还能“做”(Tool),还能“知道更多”(Resource)。
你可能会想:“这不就是API调用吗?” 不,API是接口数据交互,而MCP是上下文扩展,它把Tool和Resource当作LLM的“外挂大脑”,让Agent在推理时能动态调用或参考。
架构概览图(Mermaid)
Agent可以同时连接多个MCP Server,每个Server可以暴露任意数量的Tool和Resource。
三、实战:手写一个MCP Server(Node.js版)
我们从最基础的开始,实现一个简单的MCP Server,提供用户查询工具和静态资源。
3.1 项目初始化
mkdir mcp-demo && cd mcp-demo
npm init -y
npm install @modelcontextprotocol/sdk zod
3.2 编写 Server:my-mcp-server.mjs
先看完整代码,再逐段解析:
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: 'zh', email: 'zh@qq.com', role: 'admin' },
'002': { id: '002', name: 'gg', email: 'gg@qq.com', role: 'user' },
'003': { id: '003', name: 'xh', email: 'xh@qq.com', role: 'user' },
}
};
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0'
});
// ---------- 注册 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` }
]
};
}
return {
content: [
{
type: 'text',
text: `用户 ID ${userId} 信息是:
姓名:${user.name}
邮箱:${user.email}
角色:${user.role}`
}
]
};
});
// ---------- 注册 Resource ----------
server.registerResource(
'使用指南',
'docs://guide', // URI 标识
{
description: 'MCP Server 使用指南',
mimeType: 'text/plain',
},
async () => {
return {
contents: [
{
uri: 'docs://guide',
mimeType: 'text/plain',
text: `
MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 Cursor 等 MCP Client 中通过自然语言对话,Cursor 会自动调用相应工具。
`
}
]
};
}
);
// ---------- 启动 stdio 传输 ----------
const transport = new StdioServerTransport();
await server.connect(transport);
关键点解读
StdioServerTransport:通过标准输入输出与父进程通信。当你的Agent以子进程方式启动这个Server时,双方通过stdin/stdout交换JSON-RPC消息。- Tool注册:用
registerTool定义名称、描述、参数Schema(zod校验),执行逻辑返回content数组,支持多种类型(如text、image)。 - Resource注册:
registerResource定义URI(类似URL,但用于本地标识),提供只读数据。Resource可以当作静态文档、配置、知识库片段等,Client可以读取并注入到System Prompt中。
3.3 Server 核心执行流程
四、Agent集成:用LangChain MCP适配器调用Server
有了Server,我们还需要一个Agent(Client)来消费它。这里我们用LangChain + MCP适配器。
4.1 安装依赖
npm install @langchain/mcp-adapters @langchain/openai dotenv chalk
4.2 编写 Client:langchain-mcp-test.mjs
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';
// 1. 创建 MCP Client,配置要连接的 Server
const mcpClient = new MultiServerMCPClient({
mcpServers: {
'my-mcp-server': {
command: 'node',
args: ['E:\workspace\lgl_ai\ai\agent_in_action\mcp-demo\src\my-mcp-server.mjs']
}
}
});
// 2. 初始化 LLM(DeepSeek为例)
const model = new ChatOpenAI({
modelName: 'deepseek-v4-flash',
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: 'https://api.deepseek.com/v1',
}
});
// 3. 从 MCP Server 获取工具和资源
const tools = await mcpClient.getTools();
const resourcesMap = await mcpClient.listResources();
// 4. 读取所有资源内容,拼成 System Prompt
let resourceContent = '';
for (const [serverName, resources] of Object.entries(resourcesMap)) {
for (const resource of resources) {
const content = await mcpClient.readResource(serverName, resource.uri);
resourceContent += content[0].text;
}
}
console.log('资源内容:\n', resourceContent);
// 5. 绑定工具到模型
const modelWithTools = model.bindTools(tools);
// 6. Agent 循环
async function runAgentWithTools(query, maxIterations = 30) {
const messages = [
new SystemMessage(resourceContent), // 资源作为系统消息
new HumanMessage(query)
];
for (let i = 0; i < maxIterations; i++) {
console.log(chalk.bgGreen(`正在等待AI思考, 第 ${i + 1} 轮....`));
const response = await modelWithTools.invoke(messages);
messages.push(response);
if (!response.tool_calls || response.tool_calls.length === 0) {
console.log(`\n AI 最终回复: ${response.content}`);
return response.content;
}
console.log(chalk.bgBlue(`检测到 ${response.tool_calls.length} 个工具调用: ${response.tool_calls.map(t => t.name).join(', ')}`));
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
}));
}
}
}
return messages[messages.length - 1].content;
}
// 执行测试
// await runAgentWithTools('查一下用户002的信息');
await runAgentWithTools('MCP Server使用指南是什么?');
// 7. 关闭所有连接,释放子进程
await mcpClient.close();
4.3 代码要点剖析
MultiServerMCPClient:可以管理多个MCP Server的连接,每个Server通过command+args启动子进程,基于stdio通信。getTools():自动将Server注册的Tool转换为LangChain兼容的StructuredTool,可直接调用。listResources()+readResource():读取所有Resource的内容,我们将其拼成SystemMessage,作为LLM的固定上下文。这是除RAG之外另一种丰富上下文的手段。- Agent循环:标准ReAct风格,模型自主决策是否调用工具,直到不再调用工具为止。
- 释放资源:
mcpClient.close()会终止子进程并清理通信通道,避免脚本一直挂起。
4.4 Client 核心执行流程
4.5 stdio通信时序图
五、Resource的妙用:不只是静态文档
细心的读者会发现,我们把Resource内容直接塞进了SystemMessage。这有什么好处?
- 轻量上下文增强:对于不常变化的知识(如使用指南、API文档、业务规则),没必要每次都走RAG检索,直接作为System Prompt的一部分,既省时又省钱。
- 扩展性:Resource可以是本地文件、数据库查询结果、甚至远程API返回的只读数据,只要Server注册了对应URI,Client就能读取。
金句:Tool让Agent能“动手”,Resource让Agent能“懂行”。两者结合,才能真正扩展LLM的认知边界。
六、踩坑与进阶思考
6.1 stdio通信注意事项
- 子进程必须通过
stdio传输,确保stdout只输出MCP协议消息,避免夹杂console.log等调试信息,否则解析会失败。 - 若Server启动失败,Client可能卡住,建议加超时机制。
6.2 多语言支持
MCP协议不限制语言。你可以用Python、Rust、Java等实现Server,只要遵循JSON-RPC消息规范,Agent都能通过stdio或HTTP调用。这正是MCP最大的魅力——工具生态不再受技术栈约束。
6.3 远程Server
将transport替换为SSEServerTransport或WebSocketServerTransport,即可支持HTTP远程调用,配合身份认证,企业级分布式Agent架构就搭建起来了。
七、总结:MCP让Agent开发进入“乐高时代”
我们从一个痛点出发,理解了MCP如何通过标准化协议解耦LLM和工具/资源。通过实战代码,你学会了:
- 用Node.js实现一个MCP Server,注册Tool和Resource。
- 用LangChain的MCP适配器作为Client,集成到Agent中。
- 利用Resource注入System Prompt,丰富上下文。
- 通过流程图和时序图,深入理解跨进程通信和Agent循环机制。
MCP的本质,是给AI Agent打造了一个“万能工具箱”的接口标准。从此,不同语言、不同团队开发的工具可以像积木一样自由组合,极大提升了Agent的可扩展性和复用性。
未来,随着MCP生态的成熟,我们或许会看到大量公共MCP Server出现,就像今天的NPM或PyPI一样,让Agent开发真正进入“乐高时代”。