MCP 协议实战博客:跨进程、跨语言可复用 AI 工具标准化方案

前言

做 AI Agent 开发长期有两个致命痛点:

  1. 工具代码和主项目强耦合,换一套 Agent 就要复制、重写适配层,完全无法复用;
  2. 工具受开发语言限制,Node 写的工具,Java/Python/Rust 程序不能直接调用,跨语言通信成本极高。

传统两种方案都有短板:

  • 工具内嵌主进程:耦合严重、复用性差;
  • HTTP 接口封装工具:只是单纯拿数据,无法原生融入 LLM 上下文体系。

Model Context Protocol(MCP,模型上下文协议) 专门解决以上问题:一套标准化通信协议,让 Agent 以跨进程方式调用任意语言、独立部署的工具与静态资源,实现 LLM、工具、上下文彻底解耦。

本文分模块拆解服务端、客户端逻辑,不堆砌大段代码,逐段讲清原理与作用。

一、MCP 基础核心概念

1. MCP 是什么

一套标准化通信规范,负责打通 LLM 客户端(Agent)和外部能力服务端,统一交互格式。 服务端对外提供两类核心能力:

  • Tool:可执行工具函数,LLM 按需主动调用;
  • Resource:静态文本资源,提前注入模型上下文。

2. 两种通信传输层

  1. Stdio(本地) 利用操作系统标准输入输出流做 IPC 父子进程通信,无需端口,轻量无额外开销,本地工具首选。
  2. HTTP(远程) 跨机器网络调用,适合分布式、远程部署的 MCP 服务。

3. 核心优势:跨进程解耦

传统工具:和 Agent 跑在同一个进程,依赖、生命周期、代码全部绑定。 MCP 架构拆分两端,完全隔离:

  • MCP Server:独立可执行程序,Node/Python/Java/Rust 都能实现,只暴露 Tool/Resource;
  • MCP Client(Agent 宿主):LLM 主程序,可同时连接多个 MCP 服务,统一调用所有外部能力。

4. 和其他技术区分

  1. 对比 HTTP 接口 HTTP 仅返回原始数据;MCP 把工具、文档资源纳入 LLM 原生上下文,模型自动识别、自主决定调用时机。
  2. 对比 RAG RAG 擅长长文档检索;MCP Resource 是轻量化固定短文本(使用手册、规则),直接塞进系统提示词,二者互补使用。

二、MCP Server 服务端拆解(独立工具进程)

文件:my-mcp-server.mjs 作用:单独运行的子进程,对外暴露查询用户工具 + 使用文档资源,和 Agent 主程序无任何耦合。

1. 依赖导入

javascript 复制代码
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
  • McpServer:MCP 服务端核心实例,用于注册工具、资源;
  • StdioServerTransport:stdio 标准流通信载体,实现跨进程 IPC;
  • zod:参数校验库,标准化工具入参结构,给 LLM 清晰参数描述。

2. 模拟业务数据源

bash 复制代码
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' },
  }
}

业务逻辑与 MCP 协议层分离,后续可无缝替换真实数据库,不改动 MCP 注册代码。

3. 初始化 MCP 服务实例

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

定义服务唯一标识、版本,客户端多服务场景区分不同工具服务。

4. 注册 Tool 工具:query_user

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

三层结构拆解:

  1. 工具名:query_user,客户端识别调用的唯一标识;
  2. 元数据:description 给 LLM 看工具用途,inputSchema 定义入参格式、说明;
  3. 回调函数:接收 LLM 传入参数,执行业务逻辑,统一返回{content: [{type: "text"}]}标准 MCP 格式文本。

内部业务逻辑:

  • 根据 userId 匹配用户数据;
  • 不存在则返回提示文本,告知可用 ID;
  • 存在则拼接用户信息文本返回给客户端。

5. 注册 Resource 静态资源

php 复制代码
server.registerResource(
  '使用指南',
  'docs://guide',
  { description: 'MCP Server 使用指南', mimeType: 'text/plain' },
  async () => { return { contents: [{uri, text}] } }
)

参数说明:

  1. 资源展示名称;
  2. 资源唯一 URI 标识,客户端通过 URI 读取资源;
  3. 资源描述、文本类型;
  4. 回调:返回固定静态文本(工具使用手册、业务规范)。

作用:客户端启动时一次性读取全部 Resource,拼接成 SystemMessage 注入模型,相当于提前给 AI 下发业务规则。

6. 绑定 stdio 跨进程通信通道

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

关键代码:把 MCP 服务挂载到标准输入输出流。 客户端通过 node 命令拉起本文件作为子进程,父子进程依靠 stdio 双向收发 MCP 协议消息,完成跨进程通信。

三、MCP Client Agent 客户端拆解(LLM 主程序)

文件:langchain-mcp-test.mjs 作用:AI 主程序,作为 MCP 客户端,自动拉起 MCP 子进程、读取资源、绑定跨进程工具、自动循环调用工具回答用户问题。

1. 依赖导入

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';
  • dotenv:读取环境变量存储大模型密钥;
  • MultiServerMCPClient:多 MCP 服务统一管理客户端,支持同时连接多个不同语言的 MCP 服务;
  • ChatOpenAI:兼容 OpenAI 接口的大模型封装(DeepSeek / 通义千问均可);
  • chalk:控制台彩色日志,方便调试工具调用流程;
  • 消息类:LangChain 标准消息结构,区分系统提示、用户提问、工具返回结果。

2. 初始化大模型

php 复制代码
const model = new ChatOpenAI({
  modelName:'deepseek-v4-pro',
  apiKey: process.env.DEEPSEEK_API_KEY,
  temperature: 0,
  configuration: { baseURL: 'https://api.deepseek.com/v1' },
});

temperature=0 关闭随机性,保证工具调用稳定;通过兼容接口对接国产大模型。

3. 配置 MCP 多服务客户端

arduino 复制代码
const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node',
      args: ['文件绝对路径/my-mcp-server.mjs']
    }
  }
})

核心逻辑:

  1. 配置字典,key 为服务名称;
  2. command:启动服务端程序的命令(node/python/java/rust 二进制均可);
  3. args:启动参数,填入 MCP 服务端文件路径; 客户端内部自动调用 child_process 生成子进程,自动建立 stdio 通信,无需手动管理进程。 支持同时配置多个不同语言、不同路径的 MCP 服务。

4. 拉取工具 & 读取全部 Resource 资源

4.1 获取所有跨进程工具

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

客户端主动向所有 MCP 服务拉取工具元数据,自动封装成 LangChain 标准 Tool 实例,可直接绑定到大模型。

4.2 遍历读取所有静态资源,拼接系统提示词

ini 复制代码
const allResources = await mcpClient.listResources();
let resourceContent = '';
for (const [serverName, resources] of Object.entries(allResources)) {
  for (const resource of resources) {
    const contentList = await mcpClient.readResource(serverName, resource.uri)
    resourceContent += contentList[0].text;
  }
}

流程拆解:

  1. listResources:查询所有 MCP 服务注册的资源列表;
  2. 双层循环遍历每个服务、每个资源;
  3. readResource:根据服务名 + URI 读取资源文本;
  4. 全部文本拼接为一段完整上下文,存入 SystemMessage 给模型。

5. 模型绑定全部跨进程工具

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

LangChain 标准化绑定,模型自动感知所有 MCP 暴露的跨进程工具,输出标准 tool_calls 调用结构。

6. Agent 自动工具循环函数 runAgentWithTools

入参与初始化消息

javascript 复制代码
async function runAgentWithTools(query, maxIterations=30) {
  const messages = [
    new SystemMessage(resourceContent),
    new HumanMessage(query)
  ];
  • maxIterations:最大工具调用轮次,防止无限循环死循环;
  • messages 消息队列:SystemMessage 存放 Resource 手册,HumanMessage 存放用户原始提问。

循环推理逻辑

ini 复制代码
for (let i = 0; i < maxIterations; i++) {
  const response = await modelWithTools.invoke(messages);
  messages.push(response);

每一轮循环:把完整消息发给大模型,保存模型返回结果到消息队列。

判断是否需要调用工具

vbscript 复制代码
if (!response.tool_calls || response.tool_calls.length === 0) {
  console.log(`AI 最终回复:${response.content}`);
  return response.content;
}

模型无 tool_calls,说明不需要再调用工具,直接输出自然语言回答,终止循环。

执行跨进程工具调用,回填结果

ini 复制代码
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
    }))
  }
}

执行流程:

  1. 遍历模型输出的每一条工具调用指令;
  2. 在 MCP 拉取的工具列表匹配对应工具;
  3. invoke 内部自动通过 stdio 把参数传给 MCP 子进程执行;
  4. 拿到服务端返回文本,封装 ToolMessage,携带 tool_call_id 存入消息队列;
  5. 进入下一轮循环,模型结合工具结果重新推理。

循环上限兜底

循环达到 30 次仍未给出最终回答,直接返回最后一轮模型输出,避免卡死。

7. 执行测试 & 释放进程资源

csharp 复制代码
// 读取Resource测试
await runAgentWithTools('MCP Server的使用指南是什么?');
// 查询用户工具测试
// await runAgentWithTools('查一下用户002的信息');

// 关键:关闭所有MCP子进程
await mcpClient.close();

close () 作用:销毁所有自动拉起的 MCP 子进程、断开 stdio 通信通道,防止脚本执行完成后后台残留进程,程序正常退出。

四、完整运行链路(Stdio 本地跨进程)

  1. 执行客户端脚本,MultiServerMCPClient 通过 child_process 生成 MCP 服务端子进程;
  2. 父子进程绑定标准输入输出,建立 MCP 协议双向通信通道;
  3. 客户端主动拉取服务端全部 Tool、Resource 元数据;
  4. 读取所有 Resource 拼接为 SystemMessage,作为模型初始上下文;
  5. 用户提问送入消息队列,大模型判断是否调用工具;
  6. 工具调用参数通过 stdio 下发到子进程 MCP Server 执行业务逻辑;
  7. 工具结果原路返回主进程,封装 ToolMessage 追加到消息列表;
  8. 模型结合工具返回内容二次推理,无工具调用则输出最终回答;
  9. 任务结束调用 close,销毁全部子进程,程序退出。

五、MCP 如何解决开篇两大痛点

痛点 1:工具绑定项目,无法跨项目复用

MCP Server 是独立可执行程序,和 Agent 业务完全隔离:

  1. 单独打包部署,任意 Agent 项目仅需在 Client 配置中添加服务启动命令即可复用;
  2. 多套 Agent、多个业务系统可共用同一套 MCP 工具服务,无需复制工具代码。

痛点 2:仅 Node 可用,无法兼容 Java/Python/Rust 工具

MCP 是纯协议、语言无关:

  1. 传输层仅依赖 stdio 或 HTTP,任意语言实现 MCP SDK 即可作为 Server;
  2. 客户端统一封装,上层 Agent 完全无感知底层工具开发语言,调用逻辑不变。

六、拓展落地优化方向

  1. 远程部署改造 当前示例为本地 stdio 进程,分布式场景替换 HTTP 传输层,MCP 服务部署云端远程调用;
  2. Resource 与 RAG 搭配 Resource 适合短规范、工具手册;长业务知识库使用 RAG 检索,二者组合构建完整上下文;
  3. 多服务模块化拆分 MultiServerMCPClient 支持同时挂载数据库、文件处理、接口请求等多个独立 MCP 服务,工具按业务拆分;
  4. 生产环境增强 增加进程保活、调用超时、日志收集、权限校验,防止子进程崩溃阻塞 Agent。
相关推荐
糖墨夕2 小时前
第一章:为什么你要学 AI Agent?—— 从"切图仔"到全栈的进化之路
前端·javascript·vue.js
自律懒人2 小时前
ChatGPT Work 上线当天实测:GPT-5.6 Sol 把 Codex 塞进桌面应用,跨应用自动做报表和 PPT,一个指令干 3 小时的活
javascript·开源
不听话坏11 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
小二·12 小时前
React 19 + Next.js 15 现代前端开发实战:App Router / Server Components / 流式渲染
前端·javascript·react.js
CHNE_TAO_EMSM14 小时前
Android studio 打开文件时自动下载源码
前端·javascript·android studio
竹林81816 小时前
从 ethers.js 迁移到 Viem:一个签名验证 Bug 让我彻底放弃旧爱
javascript
你挚爱的强哥17 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
半个落月17 小时前
用 LangChain.js 手写一个能读写文件和执行命令的 Mini Cursor
javascript·人工智能·后端
天外天-亮17 小时前
HBuilder X 使用 uview-plus 方式
开发语言·javascript·hbuilder x·uview-plus