首页
看点啥
插画图片
首页 看点啥 MCP协议深度解析:从代码出发:理解AI工具的万能接口

MCP协议深度解析:从代码出发:理解AI工具的万能接口

2026-07-13 0

MCP协议深度解析:从代码出发,理解AI工具的"万能接口"

开篇:一个直击灵魂的问题

假设你是个AI大模型,用户问你:

MCP协议深度解析:从代码出发,理解AI工具的"万能接口"

"帮我查一下用户001的信息"

你知识渊博,但你对这个"数据库"一无所知。怎么办?

传统做法是:开发者给你写个函数,你调用它。

 复制代码// 传统方案:硬编码
function queryUser(userId) {
    return database.users[userId];  // 只能在这个项目用
}

问题来了

  1. 换个项目,这个函数就废了(项目绑定
  2. Node写的工具,Python项目怎么用?(语言绑定
  3. 工具和AI在同一个进程,想分开部署?没门!(进程绑定

这就好比你有个万能工具箱,但每个工具都焊死在特定的工作台上——换个地方干活就得重买工具!


MCP:给AI的"USB-C接口"

MCP (Model Context Protocol) 就是来解决这个问题的。它让AI和工具之间有了标准化的通信协议,就像USB-C一样——不管是什么设备,插上就能用!

MCP的三大核心特点

关键理解


深度解剖:MCP代码

第一步:创建MCP服务器

 复制代码import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';

这三行导入了什么?

导入作用通俗理解
McpServerMCP服务器核心类工具箱本体
StdioServerTransportstdio传输层通过"对讲机"(标准输入输出)通信
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',
});

关键理解

第四步:注册工具(核心!)

 复制代码server.registerTool(
    'query_user',  // ① 工具名称
    {              // ② 工具描述和参数定义
        description: '查询数据库中的用户信息。输入用户ID,返回该用户详细信息(姓名、邮箱、角色)',
        inputSchema: z.object({
            id: z.string().describe('用户ID, 例如: 001, 002, 003'),
        }),
    },
    async ({ id }) => {  // ③ 工具执行函数(注意:这里是解构参数!)
        // 业务逻辑...
    }
);
深入理解这三个部分

① 工具名称 'query_user'

② 工具描述(元数据)

 复制代码{
    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通信

  1. StdioServerTransport():创建一个stdio传输通道
  2. server.connect(transport):将服务器连接到这个通道
  3. 服务器开始监听 process.stdin(标准输入)
  4. 准备通过 process.stdout(标准输出)发送响应

就像:你打开对讲机,调到"stdio频道",然后说"我准备好了,谁呼叫我都能应答"


完整的通信流程

我的代码(作为子进程)

 复制代码// server.js - 这是你的MCP服务器代码
const transport = new StdioServerTransport();
await server.connect(transport);

这段代码的作用

  1. 创建一个stdio传输通道
  2. 让服务器监听 process.stdin(等待指令)
  3. 准备通过 process.stdout(返回结果)
  4. 你的程序现在是一个"被调用方"

谁来调用你?(客户端视角)

 复制代码// 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);

这段代码的作用

  1. 通过 child_process.spawn() 创建子进程
  2. 子进程运行的就是你的 server.js
  3. 通过子进程的 stdin 发送请求
  4. 通过子进程的 stdout 接收响应

完整的通信流程图


关键理解:为什么是"stdio"?

stdio的三条"管道"

 复制代码stdin  ← 父进程写,子进程读 (请求)
stdout → 子进程写,父进程读 (响应)
stderr → 子进程写,父进程读 (错误日志)

好处

  1. 零依赖:任何编程语言都支持标准输入输出
  2. 简单可靠:不需要配置端口、IP
  3. 进程隔离:工具崩溃不影响AI主进程
  4. 调试方便:可以手动输入测试

代码中的"精髓"

1. Zod参数校验

 复制代码inputSchema: z.object({
    id: z.string().describe('用户ID, 例如: 001, 002, 003'),
})

2. 错误处理

 复制代码if (!user) {
    return {
        content: [
            {
                type: 'text',
                text: `用户${id}不存在。可用ID: 001, 002, 003`
            }
        ]
    };
}

优雅的错误返回

3. 返回格式规范

 复制代码return {
    content: [
        {
            type: 'text',
            text: `用户${id}的信息如下:姓名: ${user.name} ...`
        }
    ]
};

扩展思考:工具还能怎么改进?

改进1:支持批量查询

 复制代码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')}`
            }]
        };
    }
);

改进2:添加缓存

 复制代码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 }]
        };
    }
    // ... 错误处理
}

改进3:支持多种输出格式

 复制代码return {
    content: [
        {
            type: 'text',
            text: `用户信息:${JSON.stringify(user)}`
        },
        {
            type: 'resource',  // 也可以是 image, audio 等
            resource: {
                uri: `data:user/${id}`,
                mimeType: 'application/json',
                text: JSON.stringify(user)
            }
        }
    ]
};

改进4:添加工具执行日志

 复制代码async ({ id }) => {
    console.error(`[${new Date().toISOString()}] 查询用户: ${id}`); // stderr输出
    
    // ... 业务逻辑
    
    console.error(`[${new Date().toISOString()}] 查询完成`);
    return { content: [...] };
}

MCP vs 传统方式:本质区别

传统方式(函数调用)

 复制代码// 在同一进程内
function queryUser(id) { return database.users[id]; }
const result = queryUser('001');  // 直接调用

必须在同一进程
必须用同一种语言
工具和业务耦合

MCP方式(协议调用)

 复制代码// 跨进程,跨语言
// Node.js 写的工具
server.registerTool('query_user', ...);// 任何语言写的客户端都能调用
// Python客户端:
# client.call_tool('query_user', {'id': '001'})
// Java客户端:
// client.callTool("query_user", Map.of("id", "001"))

可以跨进程
可以跨语言
工具独立部署


让代码"活"起来:实际运行流程

1. 启动MCP服务器(调试模式)

 复制代码# 可以直接运行测试
node my-mcp-server.js
# 服务器启动,等待stdin输入

2. 手动测试(通过stdio)

 复制代码# 在另一个终端,向stdin发送JSON请求
echo '{"method":"call_tool","params":{"name":"query_user","arguments":{"id":"001"}}}' | node my-mcp-server.js

3. AI Agent连接服务器

 复制代码// AI Agent(客户端)
const transport = new StdioClientTransport({
    command: 'node',
    args: ['my-mcp-server.js']
});
await client.connect(transport);

4. AI调用工具

 复制代码// AI看到用户问题"查一下001"
// AI判断:需要调用 query_user 工具
const result = await client.callTool({
    name: 'query_user',
    arguments: { id: '001' }
});

总结:在MCP生态中的位置

核心要点回顾

  1. MCP让工具"独立"了:不再绑定特定项目/语言/进程
  2. stdio是桥梁:通过标准输入输出实现跨进程通信
  3. 描述很重要:description是给AI看的,决定AI会不会用你的工具
  4. 格式要规范:返回格式必须符合MCP协议
  5. 错误要友好:返回错误信息而不是抛出异常
  6. 参数需解构async ({ id }) 而不是 async (id)

下一步你可以做什么?

  1. 改造现有工具:把你项目里的工具函数改成MCP Server
  2. 多语言尝试:用Python/Java/Rust也实现一个MCP Server
  3. 组合工具:一个MCP Server里注册多个工具
  4. 添加资源:除了Tool,还可以注册Resource(让AI获取知识)
  5. 生产部署:使用HTTP Transport部署到云端,实现远程调用

完整代码(可直接运行)

 复制代码// 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服务器已启动,等待请求...');

喜欢(0)

上一篇

深度解析 MCP 协议:推动 AI Agent 工具调用突破边界

深度解析 MCP 协议:推动 AI Agent 工具调用突破边界

下一篇

MCP 跨进程工具调用深度解析:自 LangChain Tool 的局限至自定义数据库查询 Server 实战

MCP 跨进程工具调用深度解析:自 LangChain Tool 的局限至自定义数据库查询 Server 实战
猜你喜欢