MCP协议深度解析:从代码实例看跨进程工具调用

MCP协议深度解析:从代码实例看跨进程工具调用

一、MCP协议的核心思想

readme.md中我们可以提炼出MCP协议的核心价值:

MCP(Model Context Protocol)是一个标准化LLM与工具(Tool)、资源(Resource)之间通信的协议,核心目标是实现LLM与工具的完全解耦。

想象一下,在没有MCP之前,我们的工具调用是什么样的?

javascript

javascript 复制代码
// 传统方式:工具被硬编码在项目中
function queryUser(userId) {
    // 只能在当前项目中使用
    return database.users[userId];
}

// LLM调用时必须通过固定接口
const result = queryUser('001');

这种方式存在两个致命问题:

  1. 工具无法复用 - 每个项目都需要重新实现
  2. 语言绑定 - Node.js项目只能用Node.js写的工具

而MCP的出现,彻底改变了这一局面。下面这张图清晰地展示了MCP协议在跨语言调用中的桥梁作用:

图示说明:用户通过LLM发起请求,LLM决定调用某个工具(例如Java写的工具)。通过MCP协议(stdio或http),Agent可以跨进程调用本地Java进程或远程Java服务。这使得LLM能够无缝调用不同语言编写的工具,实现了真正的语言无关性。

二、MCP的两种通信方式

MCP协议支持两种通信方式,分别对应不同的使用场景:

通信方式 适用场景 特点
stdio 本地进程间通信 低延迟、无需网络、适合本地工具
HTTP 远程进程间通信 支持分布式部署、适合微服务架构

下面的架构图展示了MCP Client与MCP Server之间的完整调用关系:

图示说明:整体架构分为三层:

  • 左侧:User和LLM构成交互层
  • 中间 :通过tool_callstools建立连接,MCP Client负责协议转换
  • 右侧:支持两种Server部署方式------本地MCP Server(stdio通信)和远程MCP Server(http通信)

三、MCP Server端实现解析

让我们从my-mcp-server.mjs开始,看看一个MCP Server是如何构建的:

3.1 创建MCP Server实例

javascript

arduino 复制代码
const server = new McpServer({
    name: 'my-mcp-server',
    version: '1.0.0',
});

这里创建了一个名为my-mcp-server的MCP服务器实例。这个命名很重要,因为在客户端配置时需要通过这个名字来引用该服务器。

3.2 注册Tool

javascript

scss 复制代码
server.registerTool('query_user', {
    description: `查询数据库中的用户信息.输入用户ID,返回该用户的详细信息(姓名,邮箱,角色)`,
    inputSchema: z.object({
        userId: z.string().describe('用户ID, 例如: 001')
    })
}, async ({ userId }) => {
    // 工具实现逻辑
});

这里注册了一个名为query_user的工具,包含了三个关键要素:

  1. 工具名称query_user - 用于客户端调用时的标识
  2. 工具描述:告诉LLM这个工具是做什么的,什么时候应该使用它
  3. 输入Schema:使用Zod定义参数结构,确保类型安全

知识点:MCP使用Zod进行参数验证,这保证了跨语言调用时的类型安全。无论客户端用什么语言,传入的参数都会按照Schema进行验证。

3.3 工具实现与返回结果

javascript

javascript 复制代码
const user = database.users[userId];
if(!user) {
    return {
        content:[
            {
                type: 'text',
                text: `用户 ${userId} 不存在. 可用的用户ID: ${Object.keys(database.users).join(', ')}`,
            }
        ]
    }
}
return {
    content:[
        {
            type: 'text',
            text: `用户 ${user.id} 信息: ${JSON.stringify(user)}`,
        }
    ]
}

这里返回的结果格式遵循MCP协议规范,所有结果都封装在content数组中,每个内容项可以指定类型(如text)。

知识点:MCP的返回结果格式是标准化的,这使得不同语言编写的Server都能返回统一格式的结果,LLM可以无差别地处理。

3.4 启动服务器(进程通信)

javascript

ini 复制代码
const transport = new StdioServerTransport();
await server.connect(transport);

这是最关键的一步!StdioServerTransport使用标准输入输出流(stdio)进行进程间通信。这意味着:

  • Server进程通过stdin接收请求
  • Server进程通过stdout返回响应
  • 所有错误信息输出到stderr

知识点:stdio通信方式是MCP跨进程调用的基础。任何语言(Node.js、Python、Java、Rust等)都支持标准输入输出流,因此都可以实现MCP Server。

结合第二张架构图,这里的StdioServerTransport对应图中"本地MCP Server"与"MCP Client"之间的stdio连接。

四、MCP Client端实现解析

再看langchain-mcp-test.mjs,这是一个使用LangChain框架的MCP客户端:

4.1 配置MultiServerMCPClient

javascript

arduino 复制代码
const mcpClient = new MultiServerMCPClient({
    mcpServers: [
        {
            'my-mcp-server':{
                command: 'stdio://localhost:3000',
                args: [],
            },
        },
    ],
});

这里配置了一个MCP服务器连接。注意command: 'stdio://localhost:3000',虽然看起来像URL,但实际上是告诉客户端通过stdio方式连接到本地进程。

知识点:MCP支持两种通信方式:

  • stdio:本地进程间通信,适用于本地工具调用(对应架构图左侧的本地MCP Server)
  • HTTP/SSE:远程进程间通信,适用于远程工具调用(对应架构图右侧的远程MCP Server)

这实现了"一次编写,到处运行"的理念 - 同样的工具可以本地运行,也可以部署为远程服务。

4.2 获取工具并绑定到模型

javascript

ini 复制代码
const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);

这是LangChain框架的优雅之处:

  1. mcpClient.getTools() 自动从MCP Server获取所有可用的工具定义
  2. model.bindTools(tools) 将这些工具绑定到LLM模型上

知识点:工具绑定后,LLM会自动理解何时应该调用哪个工具。这是通过工具的描述信息实现的 - LLM会根据用户的query和工具的描述来决定是否调用工具。

4.3 执行Agent

javascript

javascript 复制代码
async function runAgentWithTools(query, maxIterations=30) {
    const messages = [
        new SystemMessage({
            content: '你是一个专业的数据库查询助手. 你可以根据用户的问题,查询数据库中的用户信息. 你只能查询用户信息, 不能执行其他操作.',
        }),
        new HumanMessage({
            content: query, 
        }),
    ];
    const response = await modelWithTools.invoke(messages);
    console.log(chalk.green('Response:'));
    console.log(response.content);
}

这里的核心流程是:

  1. 构造对话消息(系统提示 + 用户问题)
  2. 调用绑定了工具的模型
  3. 模型自动判断是否需要调用工具
  4. 如果需要,MCP Client会通过stdio与Server通信执行工具
  5. 工具执行结果返回给模型
  6. 模型生成最终回复

五、完整的跨进程调用流程

结合两张架构图和两端代码,完整的调用流程如下:

text

arduino 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    Agent进程 (MCP Client)                       │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  1. 用户提问: "查询用户001的信息"                        │   │
│  │  2. LLM分析: 需要使用query_user工具                     │   │
│  │  3. MCP Client通过stdio发送: {                          │   │
│  │        "tool": "query_user",                            │   │
│  │        "params": {"userId": "001"}                      │   │
│  │     }                                                   │   │
│  └──────────────────────────┬───────────────────────────────┘   │
│                             │                                    │
│                    ┌────────▼────────┐                          │
│                    │   stdio通信     │                          │
│                    │  (跨进程)       │                          │
│                    └────────┬────────┘                          │
└─────────────────────────────┼───────────────────────────────────┘
                              │
┌─────────────────────────────▼───────────────────────────────────┐
│               MCP Server进程 (my-mcp-server.mjs)               │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  1. 接收stdin请求                                       │   │
│  │  2. 解析请求: tool=query_user, userId=001              │   │
│  │  3. 执行数据库查询: database.users['001']              │   │
│  │  4. 通过stdout返回结果: {"id":"001","name":"张三"...}  │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

参考第一张图,这个流程体现了MCP如何桥接不同语言环境------LLM(JavaScript环境)通过协议调用Java进程,完全不需要关心对方的语言实现细节。而第二张图则展示了整体架构中,MCP Client如何同时管理本地和远程的多个MCP Server。

六、MCP的核心优势

6.1 跨语言支持(对应图一)

MCP使用stdio作为通信基础,而所有编程语言都支持标准输入输出。这就意味着:

  • Node.js 可以调用 Python 写的工具
  • Java 可以调用 Rust 写的工具
  • 任何语言的组合都可以通过MCP协作

如图一所示,无论是通过stdio调用本地Java进程,还是通过HTTP调用远程Java服务,MCP协议都提供了统一的调用方式。

6.2 跨进程隔离(对应图二)

从图二可以看出,MCP Server独立于MCP Client运行,带来两个好处:

  1. 稳定性 - 工具崩溃不会影响Agent主进程
  2. 安全性 - 工具只能访问被授权的资源

6.3 标准化通信

MCP定义了统一的协议格式:

  • 请求格式:{ tool: string, params: object }
  • 响应格式:{ content: [{ type: string, text: string }] }

这使得任何兼容MCP的工具都可以被任何MCP客户端调用。

6.4 本地与远程统一(对应图二)

如图二所示,通过切换Transport类型,工具可以无缝地从本地迁移到远程:

javascript

ini 复制代码
// 本地调用 - 使用stdio(对应图二左侧)
const transport = new StdioServerTransport();

// 远程调用 - 使用HTTP/SSE(对应图二右侧)
// const transport = new SSEClientTransport(new URL('http://remote-server:3000'));

七、总结

MCP协议的本质是一个进程间通信协议,专门为AI Agent与外部工具、资源的交互而设计。它通过标准化的通信格式和多种传输方式(stdio/HTTP),实现了:

  1. 工具与LLM的解耦 - 工具可以独立开发、部署
  2. 跨语言调用 - 任何语言都可以实现MCP Server(如图一所示,Java工具可被Node.js LLM调用)
  3. 跨进程安全 - 工具在独立进程中运行
  4. 本地远程统一 - 同样的代码可以本地或远程部署(如图二所示)

从代码实例和架构图中我们可以看到,MCP不是简单的一个API调用,而是一个完整的生态系统。它让AI Agent能够以标准化的方式扩展自己的能力边界------无论工具是用什么语言写的、部署在本地还是远程,只要实现了MCP协议,就能被AI Agent轻松调用。这种"语言无关、位置透明"的设计理念,正是MCP协议最核心的价值所在。

相关推荐
明天有专业课1 小时前
Harness:AI Agent 的"操作系统"
人工智能·agent
柒星栈1 小时前
国产大模型“三国杀“:豆包收费、DeepSeek 开源、Kimi 2.5 万亿参数
人工智能·开源
星辰AI1 小时前
ESLint 自定义规则实战:用 AST 分析封堵团队特有的代码坏味道
人工智能·ai·语言模型
dozenyaoyida1 小时前
LSTM 自编码器原理详解:从三门结构到 STM32 端侧异常检测落地
人工智能·stm32·lstm
石榴2 小时前
我用 Codex Vibe Coding 了一个 VS Code 数据库扩展:把付费墙后的工作流写成自己的工具
人工智能
ZZZMMM.zip2 小时前
信息压缩站-长文智能摘要的HarmonyOS开发实践
人工智能·华为·harmonyos·鸿蒙·鸿蒙系统
Black蜡笔小新2 小时前
自动化AI算法训练服务器DLTM零代码私有化赋能智慧农业用AI智能视觉守护万亩良田
人工智能·算法·自动化
气泡音人声分离2 小时前
音频处理核心原理:变速与变调的本质区别,以及“变速不变调”是如何实现的
人工智能·音频剪辑·升降调·音频变速
拓向AI2 小时前
南京高性价比DeepSeek优化推广指南
大数据·人工智能