MCP 系列(01):MCP 是什么——为何 Function Calling 不够
2026-07-13 3396669
2026-07-13 0
假设你是个AI大模型,用户问你:

"帮我查一下用户001的信息"
你知识渊博,但你对这个"数据库"一无所知。怎么办?
传统做法是:开发者给你写个函数,你调用它。
复制代码// 传统方案:硬编码
function queryUser(userId) {
return database.users[userId]; // 只能在这个项目用
}
问题来了:
这就好比你有个万能工具箱,但每个工具都焊死在特定的工作台上——换个地方干活就得重买工具!
MCP (Model Context Protocol) 就是来解决这个问题的。它让AI和工具之间有了标准化的通信协议,就像USB-C一样——不管是什么设备,插上就能用!
关键理解:
复制代码import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
这三行导入了什么?
| 导入 | 作用 | 通俗理解 |
|---|---|---|
McpServer | MCP服务器核心类 | 工具箱本体 |
StdioServerTransport | stdio传输层 | 通过"对讲机"(标准输入输出)通信 |
z (zod) | 参数校验工具 | 检查工具收到的"指令"是否合规 |
复制代码const database = {
users: {
'001': {id: '001', name: '张三', email: '[email protected]', role: 'admin'},
'002': {id: '002', name: '李四', email: '[email protected]', role: 'user'},
'003': {id: '003', name: '王五', email: '[email protected]', role: 'user'}
}
}
注意:这里是模拟数据,实际项目可以换成:
复制代码const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
关键理解:
name:服务器的唯一标识,客户端通过它来识别version:版本号,便于升级管理 复制代码server.registerTool(
'query_user', // ① 工具名称
{ // ② 工具描述和参数定义
description: '查询数据库中的用户信息。输入用户ID,返回该用户详细信息(姓名、邮箱、角色)',
inputSchema: z.object({
id: z.string().describe('用户ID, 例如: 001, 002, 003'),
}),
},
async ({ id }) => { // ③ 工具执行函数(注意:这里是解构参数!)
// 业务逻辑...
}
);
① 工具名称 'query_user'
get_user, create_order, search_product② 工具描述(元数据)
复制代码{
description: '...', // 给AI看的!AI根据这个决定是否调用
inputSchema: z.object({...}) // 参数校验,防止AI"胡言乱语"
}
重点理解 description:
③ 执行函数
复制代码async ({ id }) => { // 注意:参数是一个对象,需要解构
const user = database.users[id];
if (!user) { // 用户不存在
return {
content: [
{
type: 'text',
text: `用户${id}不存在。可用ID: 001, 002, 003`
}
]
}
}
// 返回固定格式的结果
return {
content: [
{
type: 'text',
text: `用户${id}的信息如下:姓名: ${user.name} 邮箱: ${user.email} 角色: ${user.role}`
}
]
};
}
️ 常见错误:很多初学者会写成 async (id) =>,但正确写法是 async ({ id }) =>,因为参数是以对象形式传入的!
返回值格式解析:
复制代码{
content: [ // 内容数组,可以包含多种类型
{
type: 'text', // 文本类型(也支持 image, audio 等)
text: `用户${id}的信息如下:姓名: ${user.name} 邮箱: ${user.email} 角色: ${user.role}`
}
]
}
这个格式是MCP协议规定的,所有工具都必须遵守!
复制代码const transport = new StdioServerTransport();
await server.connect(transport);
它让你的MCP服务器准备好通过stdio通信:
StdioServerTransport():创建一个stdio传输通道server.connect(transport):将服务器连接到这个通道process.stdin(标准输入)process.stdout(标准输出)发送响应就像:你打开对讲机,调到"stdio频道",然后说"我准备好了,谁呼叫我都能应答"
复制代码// server.js - 这是你的MCP服务器代码
const transport = new StdioServerTransport();
await server.connect(transport);
这段代码的作用:
process.stdin(等待指令)process.stdout(返回结果) 复制代码// client.js - 这是另一个文件(AI Agent)
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';const transport = new StdioClientTransport({
command: 'node', // 执行命令
args: ['server.js'] // 你的文件
});await client.connect(transport);
这段代码的作用:
child_process.spawn() 创建子进程server.jsstdin 发送请求stdout 接收响应 复制代码stdin ← 父进程写,子进程读 (请求)
stdout → 子进程写,父进程读 (响应)
stderr → 子进程写,父进程读 (错误日志)
复制代码inputSchema: z.object({
id: z.string().describe('用户ID, 例如: 001, 002, 003'),
})
复制代码if (!user) {
return {
content: [
{
type: 'text',
text: `用户${id}不存在。可用ID: 001, 002, 003`
}
]
};
}
优雅的错误返回:
throw error(会中断通信) 复制代码return {
content: [
{
type: 'text',
text: `用户${id}的信息如下:姓名: ${user.name} ...`
}
]
};
复制代码server.registerTool(
'query_users_batch',
{
description: '批量查询用户信息,一次查询多个用户',
inputSchema: z.object({
ids: z.array(z.string()).describe('用户ID数组,例如: ["001", "002"]')
})
},
async ({ ids }) => {
const results = ids.map(id => database.users[id]).filter(Boolean);
if (results.length === 0) {
return {
content: [{ type: 'text', text: '没有找到任何用户' }]
};
}
return {
content: [{
type: 'text',
text: `找到 ${results.length} 个用户:n${results.map(u => `${u.id}: ${u.name}(${u.role})`).join('n')}`
}]
};
}
);
复制代码const cache = new Map();async ({ id }) => {
if (cache.has(id)) {
return {
content: [{
type: 'text',
text: `[缓存] ${cache.get(id)}`
}]
};
}
const user = database.users[id];
if (user) {
const userStr = `姓名: ${user.name} 邮箱: ${user.email} 角色: ${user.role}`;
cache.set(id, userStr);
return {
content: [{ type: 'text', text: userStr }]
};
}
// ... 错误处理
}
复制代码return {
content: [
{
type: 'text',
text: `用户信息:${JSON.stringify(user)}`
},
{
type: 'resource', // 也可以是 image, audio 等
resource: {
uri: `data:user/${id}`,
mimeType: 'application/json',
text: JSON.stringify(user)
}
}
]
};
复制代码async ({ id }) => {
console.error(`[${new Date().toISOString()}] 查询用户: ${id}`); // stderr输出
// ... 业务逻辑
console.error(`[${new Date().toISOString()}] 查询完成`);
return { content: [...] };
}
复制代码// 在同一进程内
function queryUser(id) { return database.users[id]; }
const result = queryUser('001'); // 直接调用
必须在同一进程
必须用同一种语言
工具和业务耦合
复制代码// 跨进程,跨语言
// Node.js 写的工具
server.registerTool('query_user', ...);// 任何语言写的客户端都能调用
// Python客户端:
# client.call_tool('query_user', {'id': '001'})
// Java客户端:
// client.callTool("query_user", Map.of("id", "001"))
可以跨进程
可以跨语言
工具独立部署
复制代码# 可以直接运行测试
node my-mcp-server.js
# 服务器启动,等待stdin输入
复制代码# 在另一个终端,向stdin发送JSON请求
echo '{"method":"call_tool","params":{"name":"query_user","arguments":{"id":"001"}}}' | node my-mcp-server.js
复制代码// AI Agent(客户端)
const transport = new StdioClientTransport({
command: 'node',
args: ['my-mcp-server.js']
});
await client.connect(transport);
复制代码// AI看到用户问题"查一下001"
// AI判断:需要调用 query_user 工具
const result = await client.callTool({
name: 'query_user',
arguments: { id: '001' }
});
async ({ id }) 而不是 async (id) 复制代码// mcp-server.js
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: '[email protected]', role: 'admin'},
'002': {id: '002', name: '李四', email: '[email protected]', role: 'user'},
'003': {id: '003', name: '王五', email: '[email protected]', role: 'user'}
}
};// 创建服务器
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});// 注册工具
server.registerTool(
'query_user',
{
description: '查询数据库中的用户信息。输入用户ID,返回该用户详细信息(姓名、邮箱、角色)',
inputSchema: z.object({
id: z.string().describe('用户ID, 例如: 001, 002, 003'),
}),
},
async ({ id }) => {
const user = database.users[id];
if (!user) {
return {
content: [
{
type: 'text',
text: `用户${id}不存在。可用ID: ${Object.keys(database.users).join(', ')}`
}
]
};
}
return {
content: [
{
type: 'text',
text: `用户${id}的信息如下:姓名: ${user.name} 邮箱: ${user.email} 角色: ${user.role}`
}
]
};
}
);// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP服务器已启动,等待请求...');