
🎯 本文目标
读完本文,你将学会:
- MCP 协议的核心概念和工作原理
- 如何开发自己的 MCP Server(工具 + 资源)
- 如何用 LangChain 调用自定义 MCP Server
- 跨进程通信的实现原理
一、为什么需要 MCP?
1.1 传统工具调用的问题
在学习 AI Agent 的过程中,我们通常会直接在代码中定义工具:
javascript
// 传统方式:工具和代码耦合
async function queryUser(userId) {
const users = {
"001": { name: "张三", email: "zh@qq.com" },
"002": { name: "李四", email: "ls@qq.com" },
};
return users[userId];
}
这种方式有两个问题:
| 问题 | 说明 |
|---|---|
| 耦合度高 | 工具只能在当前项目中使用,无法复用 |
| 语言限制 | 如果工具是 Java/Python 写的,Node.js 项目无法调用 |
1.2 MCP 的解决方案
MCP(Model Context Protocol) 提供了标准化的跨进程通信方式:
scss
┌─────────────────┐ MCP协议 ┌─────────────────┐
│ AI Agent │ ────────────────────────→ │ MCP Server │
│ (客户端) │ ←─────────────────────── │ (服务端) │
└─────────────────┘ stdio / HTTP └─────────────────┘
Node.js Python
LangChain Java
Go
核心优势:
- 跨进程:工具运行在独立进程中,不影响主进程
- 跨语言:MCP Server 可以用任何语言开发
- 即插即用:任何 MCP Server 都可以被任何 Agent 调用
1.3 MCP 的两种通信方式
| 方式 | 适用场景 | 通信原理 |
|---|---|---|
| stdio | 本地工具 | 通过标准输入输出流通信 |
| HTTP | 远程工具 | 通过网络请求通信 |
二、准备工作
2.1 创建项目
powershell
mkdir mcp-server-demo
cd mcp-server-demo
npm init -y
2.2 安装依赖
powershell
pnpm i @modelcontextprotocol/sdk @langchain/core @langchain/openai @langchain/mcp-adapters chalk zod dotenv
依赖说明:
| 依赖 | 作用 |
|---|---|
@modelcontextprotocol/sdk |
MCP 协议核心库 |
@langchain/core |
LangChain 核心库 |
@langchain/openai |
OpenAI 模型集成 |
@langchain/mcp-adapters |
LangChain MCP 适配器 |
chalk |
命令行彩色输出 |
zod |
参数校验 |
dotenv |
加载环境变量 |
2.3 配置环境变量
创建 .env 文件:
env
DEEPSEEK_API_KEY=your_deepseek_api_key
三、开发自定义 MCP Server
3.1 创建 Server 文件
创建 src/my-mcp-server.mjs:
javascript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
代码解释:
javascript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
导入 MCP Server 类,用来创建工具和资源服务。
javascript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
导入 stdio 传输类,用来实现跨进程通信。
javascript
import { z } from "zod";
导入 zod,用来定义参数校验规则。
3.2 定义数据库
javascript
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" },
},
};
这是一个模拟数据库,存储用户信息。实际项目中可以替换为真实数据库。
3.3 创建 MCP Server
javascript
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
创建一个 MCP Server 实例,指定名称和版本号。
3.4 注册工具

javascript
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: `用户 ${user.id} 的信息是:姓名: ${user.name}, 邮箱: ${user.email}, 角色: ${user.role}`,
},
],
};
},
);
代码逐行解释:
javascript
server.registerTool(name, definition, handler);
注册一个工具,接收三个参数:
name:工具名称definition:工具定义(描述和参数)handler:工具执行函数
javascript
description: `查询数据库中的用户信息...`,
工具的描述信息,AI 会根据这个描述来判断是否调用该工具。
javascript
inputSchema: {
userId: z.string().describe("用户ID, 例如:001, 002, 003"),
},
参数校验规则:
userId:字符串类型describe:参数说明,帮助 AI 理解如何传参
javascript
async ({ userId }) => { ... }
工具的执行函数,接收参数并返回结果。
javascript
return {
content: [
{
type: "text",
text: `用户信息...`,
},
],
};
返回结果格式:
content:内容数组type:内容类型(text 表示纯文本)text:返回的文本内容
3.5 注册资源
javascript
server.registerResource(
"使用指南",
"docs://guide",
{
description: "MCP Server使用指南",
mimeType: "text/plain",
},
async () => {
return {
contents: [
{
uri: "docs://guide",
mimeType: "text/plain",
text: `MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 MCP Client 中通过自然语言对话,会自动调用相应工具。`,
},
],
};
},
);
代码解释:
javascript
server.registerResource(name, uri, definition, handler);
注册一个资源,资源可以作为 AI 的上下文信息:
name:资源名称uri:资源访问路径(类似 URL)definition:资源定义handler:资源内容生成函数
资源 vs 工具:
| 类型 | 作用 | 示例 |
|---|---|---|
| Tool | AI 主动调用执行操作 | 查询用户、搜索酒店 |
| Resource | 被动提供上下文信息 | 使用指南、文档说明 |
3.6 启动 Server
javascript
const transport = new StdioServerTransport();
await server.connect(transport);
代码解释:
javascript
const transport = new StdioServerTransport();
创建 stdio 传输实例,用于进程间通信。
javascript
await server.connect(transport);
启动 Server,开始监听 stdio 输入。
四、开发 MCP Client
4.1 创建 Client 文件
创建 src/langchain-mcp-test.mjs:
javascript
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";
4.2 配置 MCP Client
javascript
const mcpClient = new MultiServerMCPClient({
mcpServers: {
"my-mcp-server": {
command: "node",
args: ["src/my-mcp-server.mjs"],
},
},
});
代码解释:
javascript
const mcpClient = new MultiServerMCPClient({ ... });
创建多服务器 MCP 客户端。
javascript
"my-mcp-server": {
command: "node",
args: ["src/my-mcp-server.mjs"],
},
配置要连接的 MCP Server:
command:启动命令(node)args:命令参数(要执行的文件路径)
当 Client 启动时,会自动通过 node src/my-mcp-server.mjs 启动一个子进程。
4.3 连接模型
javascript
const model = new ChatOpenAI({
modelName: "deepseek-v4-flash",
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: "https://api.deepseek.com/v1",
},
});
4.4 获取工具和资源
javascript
const tools = await mcpClient.getTools();
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;
}
}
代码逐行解释:
javascript
const tools = await mcpClient.getTools();
获取所有 MCP Server 提供的工具列表。
javascript
const res = await mcpClient.listResources();
获取所有 MCP Server 提供的资源列表。
javascript
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;
}
}
遍历所有资源,读取资源内容,拼接成字符串。这个内容会作为 SystemMessage 传给 AI。
4.5 绑定工具到模型
javascript
const modelWithTools = model.bindTools(tools);
代码解释:
model.bindTools(tools) 把工具绑定到模型上,这样模型就知道有哪些工具可用,会自动决定是否调用工具。
4.6 AI 思考循环
javascript
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}个工具调用`));
console.log(chalk.bgBlue(`工具调用: ${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;
}
代码解释:
- 初始化消息:SystemMessage(资源内容)+ HumanMessage(用户问题)
- AI 思考:调用模型,获取响应
- 判断是否调用工具 :
- 没有工具调用 → 返回最终答案
- 有工具调用 → 执行工具调用
- 执行工具调用 :
- 通过工具名称查找工具
- 调用工具并获取结果
- 把结果作为 ToolMessage 添加到消息历史
4.7 启动程序
javascript
await runAgentWithTools("查一下用户001的信息");
// await runAgentWithTools("MCP Server的使用指南是?");
await mcpClient.close();
代码解释:
javascript
await mcpClient.close();
关闭 MCP Client,同时会关闭所有子进程(包括 MCP Server),释放资源。
五、运行程序
5.1 启动 Server(测试)
powershell
node src/my-mcp-server.mjs
Server 启动后会等待 stdio 输入,不会有任何输出(这是正常的)。
5.2 启动 Client
powershell
node src/langchain-mcp-test.mjs
5.3 执行日志
场景1:查询用户信息
erlang
正在等待AI思考, 第 1 轮....
检测到 1个工具调用
工具调用: query_user
正在等待AI思考, 第 2 轮....
AI 最终回复: 用户 001 的信息是:姓名:祖豪, 邮箱:zh@qq.com, 角色:admin
场景2:查询使用指南
arduino
正在等待AI思考, 第 1 轮....
AI 最终回复: MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 MCP Client 中通过自然语言对话,会自动调用相应工具。
六、MCP 工作原理详解

6.1 通信流程
arduino
┌─────────────────────────────────────────────────────────────────┐
│ 1. Client 启动子进程: node src/my-mcp-server.mjs │
│ └─→ 启动 my-mcp-server 进程 │
├─────────────────────────────────────────────────────────────────┤
│ 2. Client 通过 stdio 发送初始化请求 │
│ └─→ {"jsonrpc":"2.0","id":1,"method":"initialize",...} │
├─────────────────────────────────────────────────────────────────┤
│ 3. Server 响应初始化请求 │
│ └─→ {"jsonrpc":"2.0","id":1,"result":{"capabilities":...}} │
├─────────────────────────────────────────────────────────────────┤
│ 4. Client 请求工具列表 │
│ └─→ {"jsonrpc":"2.0","id":2,"method":"tools/list"} │
├─────────────────────────────────────────────────────────────────┤
│ 5. Server 返回工具列表 │
│ └─→ {"jsonrpc":"2.0","id":2,"result":{"tools":[...]}} │
├─────────────────────────────────────────────────────────────────┤
│ 6. AI 决定调用工具,Client 发送工具调用请求 │
│ └─→ {"jsonrpc":"2.0","id":3,"method":"tools/call",...} │
├─────────────────────────────────────────────────────────────────┤
│ 7. Server 执行工具,返回结果 │
│ └─→ {"jsonrpc":"2.0","id":3,"result":{"content":[...]}} │
└─────────────────────────────────────────────────────────────────┘
6.2 stdio 通信原理
stdio(Standard Input/Output)是进程间通信的标准方式:
arduino
┌─────────────────┐ 标准输入 ┌─────────────────┐
│ 主进程 │ ───────────────→ │ 子进程 │
│ (Client) │ │ (Server) │
│ │ ←─────────────── │ │
│ │ 标准输出 │ │
└─────────────────┘ └─────────────────┘
- 标准输入 (stdin):主进程向子进程发送数据
- 标准输出 (stdout):子进程向主进程返回数据
这种方式不需要网络,速度快,适合本地工具调用。
七、常见问题与解决方案
问题1:Module not found
报错信息:
arduino
Error: Cannot find module '@modelcontextprotocol/sdk/server/stdio'
解决方案:
检查导入路径是否正确,确保安装了 @modelcontextprotocol/sdk:
powershell
pnpm add @modelcontextprotocol/sdk
正确的导入路径:
javascript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
问题2:Server 无法连接
报错信息:
vbnet
MCPClientError: Failed to connect to stdio server "my-mcp-server": ...
解决方案:
- 检查文件路径是否正确
- 确保 Server 文件能正常运行
- 检查端口是否被占用
问题3:工具调用失败
报错信息:
lua
ToolInputParsingException: Received tool input did not match expected schema
解决方案:
检查工具的 inputSchema 定义是否正确,确保 AI 传的参数类型符合要求。
问题4:进程无法退出
问题描述:程序执行完后进程一直挂着不退出
解决方案:
在程序末尾调用 mcpClient.close() 关闭所有子进程:
javascript
await mcpClient.close();
八、完整代码
8.1 MCP Server
javascript
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: `查询数据库中的用户信息。输入用户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: `用户 ${user.id} 的信息是:姓名: ${user.name}, 邮箱: ${user.email}, 角色: ${user.role}`,
},
],
};
},
);
server.registerResource(
"使用指南",
"docs://guide",
{
description: "MCP Server使用指南",
mimeType: "text/plain",
},
async () => {
return {
contents: [
{
uri: "docs://guide",
mimeType: "text/plain",
text: `MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 MCP Client 中通过自然语言对话,会自动调用相应工具。`,
},
],
};
},
);
const transport = new StdioServerTransport();
await server.connect(transport);
8.2 MCP Client
javascript
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";
const mcpClient = new MultiServerMCPClient({
mcpServers: {
"my-mcp-server": {
command: "node",
args: ["src/my-mcp-server.mjs"],
},
},
});
const model = new ChatOpenAI({
modelName: "deepseek-v4-flash",
apiKey: process.env.DEEPSEEK_API_KEY,
configuration: {
baseURL: "https://api.deepseek.com/v1",
},
});
const tools = await mcpClient.getTools();
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;
}
}
const modelWithTools = model.bindTools(tools);
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}个工具调用`));
console.log(chalk.bgBlue(`工具调用: ${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("查一下用户001的信息");
await mcpClient.close();
九、总结
你学到了什么?
- MCP 协议:标准化的跨进程通信协议,让 AI 能调用外部工具
- 开发 MCP Server:注册工具和资源,通过 stdio 提供服务
- 开发 MCP Client:配置 Server,获取工具,调用 AI 思考循环
- 通信原理:通过 stdio(标准输入输出)实现进程间通信
核心概念回顾
| 概念 | 解释 |
|---|---|
| MCP Server | 工具服务端,注册工具和资源 |
| MCP Client | 工具客户端,调用工具 |
| stdio | 进程间通信方式(标准输入输出) |
| Tool | AI 主动调用的工具 |
| Resource | AI 被动获取的上下文信息 |
下一步可以做什么?
- 添加更多工具(如数据库查询、API 调用)
- 使用 HTTP 方式连接远程 MCP Server
- 用 Python/Java 开发跨语言 MCP Server
现在动手试试吧!打造你的专属工具服务 🚀