LangChain 集成 MCP 全链路实战:MultiServerMCPClient 自动获取工具与 Resource 资源注入

LangChain 集成 MCP 全链路实战:MultiServerMCPClient 自动获取工具与 Resource 资源注入

之前我们手写了一个 MCP Server,注册了 query_user 工具。但如果要把它接入 LangChain Agent,需要手动写代码获取工具、绑定模型吗?@langchain/mcp-adaptersMultiServerMCPClient 给出了答案:一行代码自动获取所有工具和资源,让 MCP Server 和 LangChain Agent 无缝衔接。本文从 Resource 资源概念出发,完整解析 MCP 全链路集成。


前言

在之前的手写 MCP Server 文章中,我们实现了一个数据库查询 Server,注册了 query_user 工具。要把它接入 LangChain Agent,常规思路是:手动启动 MCP Server → 通过 stdio 通信 → 解析 JSON-RPC 消息 → 转换成 LangChain Tool 对象 → 绑定到模型。

太麻烦了。

@langchain/mcp-adapters 包的出现解决了这个问题。它提供了 MultiServerMCPClient,可以自动连接多个 MCP Server、自动获取所有工具和资源,然后用一行代码接入你的 LangChain 工作流。

同时,本文还会介绍 MCP 中一个被忽略但非常重要的概念------Resource(资源)。它不仅是 Tool 的补充,更是 RAG 之外丰富 LLM 上下文的另一种手段。


一、为什么需要 MultiServerMCPClient?

1.1 手动集成的麻烦

javascript 复制代码
// ❌ 手动集成的痛苦流程:
// 1. 手动启动 MCP Server 子进程
const child = spawn('node', ['my-mcp-server.mjs'], { stdio: 'pipe' });
// 2. 手动发送 JSON-RPC 消息到 stdin
// 3. 手动解析 stdout 返回的 JSON
// 4. 手动将 MCP 工具格式转换为 LangChain Tool 格式
// 5. 手动 bindTools

1.2 MultiServerMCPClient 的一行解决

javascript 复制代码
import { MultiServerMCPClient } from '@langchain/mcp-adapters';

const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node',
      args: ['/path/to/my-mcp-server.mjs']
    }
  }
});

// 自动获取所有工具
const tools = await mcpClient.getTools();
// tools 已经是 LangChain Tool 格式,直接绑定
const modelWithTools = model.bindTools(tools);

核心价值

手动集成 MultiServerMCPClient
手动启动子进程 自动 spawn
手动发送 JSON-RPC 自动协议通信
手动解析返回格式 自动格式转换
手动构造 Tool 对象 自动 getTools()
一个 Server 一套代码 一个 Client 管理多个 Server

二、MCP Resource:RAG 之外丰富上下文的手段

2.1 Tool vs Resource 的区别

在 MCP 中,有两种扩展 LLM 上下文的方式:

概念 作用 触发时机 类比
Tool 让 LLM 更多 LLM 主动判断并调用 人的手(执行动作)
Resource 让 LLM 知道更多 自动注入上下文 人的记忆(提供知识)

2.2 Resource 的本质

Resource(资源)是 MCP Server 向 Agent 暴露的静态知识片段。它们可以是:

  • 项目文档(README、API 文档)
  • 使用指南、配置说明
  • 代码规范、开发手册
  • 内部知识库文章

这些资源在 Agent 启动时自动获取,作为 SystemMessage 的一部分注入到 LLM 的上下文中,让 LLM 在回答问题时拥有更多背景知识。

2.3 Resource vs RAG

笔记中提到了一个精准的对比:

维度 Resource RAG
触发方式 启动时自动注入 查询时实时检索
数据量 较小(文档级别) 较大(知识库级别)
适用场景 使用指南、配置、规范 海量文档、FAQ
上下文窗口 受 LLM 上下文限制 可以很大(检索后摘要)
arduino 复制代码
Resource:
  Agent 启动 → 读取 docs://guide → 注入 SystemMessage
  → LLM 始终知道"这个 Server 是做什么的"

RAG:
  用户提问 → 向量化查询 → 检索相关文档 → 注入 Prompt
  → LLM 根据检索结果回答

Resource 是 RAG 之外丰富上下文的另一种手段------适合那些不需要检索、但需要 LLM 始终知道的小段文档。


三、实战:完整的 MCP Server + Resource + MultiServerMCPClient

3.1 MCP Server 端:注册 Tool + Resource

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';

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'
});

// ===== 注册 Tool =====
server.registerTool('query_user', {
  description: '查询数据库中的用户信息',
  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}`
    }]
  };
});

// ===== 注册 Resource(关键新增)=====
server.registerResource(
  '使用指南',                           // 资源名称
  'docs://guide',                      // URI(统一资源标识符)
  {                                    // 元信息
    description: 'MCP Server 使用指南',
    mimeType: 'text/plain'
  },
  async () => {                        // 资源内容提供函数
    return {
      contents: [{
        uri: 'docs://guide',
        mimeType: 'text/plain',
        text: `
MCP Server 使用指南
功能:提供用户查询等工具。
使用:在 Cursor 等 MCP Client 中通过自然语言对话,
      Client 会自动调用相应工具。
        `
      }]
    };
  }
);

// 启动 stdio 传输
const transport = new StdioServerTransport();
await server.connect(transport);

Resource 注册的关键点

参数 说明
name 资源名称(LLM 可见)
uri 统一资源标识符(如 docs://guide
metadata 描述和 MIME 类型
handler 返回资源内容的异步函数

3.2 LangChain 客户端:MultiServerMCPClient

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';

// 1. 创建 LLM
const model = new ChatOpenAI({
  modelName: 'deepseek-v4-pro',
  apiKey: process.env.DEEPSEEK_API_KEY,
  temperature: 0,
  configuration: {
    baseURL: 'https://api.deepseek.com/v1',
  },
});

// 2. 创建 MultiServerMCPClient
const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node',
      args: ['/path/to/my-mcp-server.mjs']
    }
  }
});

// 3. 自动获取所有工具
const tools = await mcpClient.getTools();
console.log(`获取到 ${tools.length} 个工具`);

// 4. 获取所有 Resource 资源
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;
  }
}
console.log(resourceContent, '---------------');

// 5. 绑定工具到模型
const modelWithTools = model.bindTools(tools);

3.3 Resource 注入 SystemMessage

javascript 复制代码
async function runAgentWithTools(query, maxIterations = 30) {
  const messages = [
    // ✅ Resource 内容作为 SystemMessage,自动注入上下文
    new SystemMessage(resourceContent),
    new HumanMessage(query)
  ];

  for (let i = 0; i < maxIterations; i++) {
    console.log(chalk.bgGreen(`正在等待AI思考, 第${i}轮....`));
    const response = await modelWithTools.invoke(messages);
    messages.push(response);

    // 没有 tool_calls → 直接回答
    if (!response.tool_calls || response.tool_calls.length === 0) {
      console.log(`\n AI 最终回复: \n ${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('查一下用户002的信息');

// 询问使用指南(Resource 内容已注入 SystemMessage)
await runAgentWithTools('MCP Server的使用指南是什么?');

3.4 资源释放:mcpClient.close()

javascript 复制代码
// 关闭所有 MCP 子进程与通信通道,释放进程资源
await mcpClient.close();

为什么必须 close()?

perl 复制代码
node langchain-mcp-test.mjs
  → 启动主进程
  → MultiServerMCPClient 内部 spawn 子进程
  → 子进程启动 my-mcp-server.mjs(stdio 连接)
  → 主进程通过 stdio 和子进程通信

如果不 close():
  → 子进程一直运行
  → 主进程退出后,子进程变成"孤儿进程"
  → 占用系统资源

close() 的作用:
  → 关闭 stdio 通信通道
  → 终止子进程
  → 释放所有相关资源

四、完整链路架构图

scss 复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                    LangChain + MCP 全链路架构                          │
├─────────────────────────────────────────────────────────────────────┤
│  LangChain 客户端(langchain-mcp-test.mjs)                           │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  MultiServerMCPClient                                         │   │
│  │  ├── mcpClient.getTools()  → 自动获取所有 Tool               │   │
│  │  ├── mcpClient.listResources() → 列出所有 Resource            │   │
│  │  ├── mcpClient.readResource() → 读取 Resource 内容           │   │
│  │  └── mcpClient.close() → 释放资源                            │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                         ↓ stdio(JSON-RPC)                         │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  ChatOpenAI + bindTools(tools)                                │   │
│  │  SystemMessage(resourceContent)  ← Resource 注入上下文         │   │
│  │  ReAct 循环:invoke → tool_calls → ToolMessage → invoke      │   │
│  └──────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│  MCP Server(my-mcp-server.mjs)                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  registerTool('query_user')                                   │   │
│  │    → 查询 database.users                                      │   │
│  │                                                               │   │
│  │  registerResource('使用指南', 'docs://guide')                │   │
│  │    → 返回静态文档内容                                         │   │
│  └──────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

五、从单个 Server 到多个 Server

MultiServerMCPClient 的核心优势之一就是支持多个 MCP Server

javascript 复制代码
const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'user-db': {
      command: 'node',
      args: ['/path/to/user-db-server.mjs']
    },
    'file-system': {
      command: 'node',
      args: ['/path/to/filesystem-server.mjs']
    },
    'weather-api': {
      command: 'node',
      args: ['/path/to/weather-server.mjs']
    }
  }
});

// 自动合并所有 Server 的工具
const tools = await mcpClient.getTools();
// tools 包含 user-db + file-system + weather-api 的所有工具

多 Server 的价值

Server 负责 语言
user-db 用户数据查询 Node.js
file-system 文件读写 Node.js
weather-api 天气查询 Python

Agent 只需要一个 MultiServerMCPClient,就能同时调用三个 Server 的工具------跨进程、跨语言、统一管理


知识树

scss 复制代码
LangChain 集成 MCP 全链路实战
├── MultiServerMCPClient 核心价值
│   ├── 自动 spawn 子进程
│   ├── 自动 JSON-RPC 通信
│   ├── 自动格式转换
│   └── 自动 getTools()
├── MCP Resource(资源)
│   ├── Tool vs Resource 区别(做 vs 知道)
│   ├── Resource vs RAG 对比(启动注入 vs 实时检索)
│   ├── registerResource(name, uri, meta, handler)
│   └── Resource 注入 SystemMessage
├── 完整链路
│   ├── Server 端:registerTool + registerResource
│   ├── Client 端:MultiServerMCPClient
│   │   ├── getTools() → bindTools
│   │   ├── listResources() + readResource()
│   │   └── close() → 释放资源
│   └── ReAct 循环:SystemMessage(resource) + HumanMessage
├── 审查纠正
│   ├── registerTool handler 参数({userId} 对象解构)
│   ├── Resource URI 拼写一致性
│   ├── Promise.all 风险提示
│   └── chalk.bgBlue 重复调用
└── 进阶:多 Server 统一管理

结语

从手写 MCP Server 到 LangChain MultiServerMCPClient,我们完成了 MCP 集成的最后一公里:

  1. MCP Server:注册 Tool + Resource,独立运行
  2. MultiServerMCPClient:自动获取工具和资源,一行代码接入
  3. Resource 注入:静态文档自动成为 SystemMessage,丰富 LLM 上下文
  4. 资源释放mcpClient.close() 防止子进程泄漏

这套组合的价值在于:MCP Server 的开发者专注于工具实现,LangChain Agent 的开发者专注于业务逻辑,两者通过标准协议解耦

MultiServerMCPClient 不是简单的封装------它代表了 AI 工程化的一个趋势:让协议层处理通信细节,让开发者专注于创造价值。


参考与拓展阅读:

  • @langchain/mcp-adapters 官方文档
  • MCP 协议规范(Model Context Protocol Specification)
  • 《MCP 跨进程工具调用深度解析》------ LangChain Tool 的局限与 MCP 解耦
  • 《手写 MCP Server 全栈实战》------ Node.js + Zod 构建工具
  • 《从零手写 AI Agent》------ LangChain Tool 注册入门

如果本文帮你理解了 LangChain 与 MCP 的集成方式和 Resource 资源的概念,欢迎点赞 + 收藏。你在用 MCP 接入什么工具?欢迎在评论区交流讨论 👇

#MCP #LangChain #MultiServerMCPClient #Resource #AIAgent #掘金技术社区

相关推荐
ServBay2 小时前
你还在手写 Prompt?聪明的人早就用上了循环工程,AI 的自动驾驶时代来了
ai编程·claude·mcp
Esaka_Forever3 小时前
StdioServerTransport 是 MCP(Model Context Protocol)模型上下文协议 官方内置的标准传输层实现
mcp
七牛开发者4 小时前
专访 DeepChat 作者们:聊聊本地优先、MCP 与 Agent Memory
agent·ai编程·mcp
Darling噜啦啦5 小时前
MCP 跨进程工具调用深度解析:从 LangChain Tool 的局限到自定义数据库查询 Server 实战
mcp
用户2930750976699 小时前
深入理解 MCP 协议:让 AI Agent 工具调用突破边界
mcp
孟无岐10 小时前
从零到可玩:用 WorkBuddy + TapTap 制造做出一款放置挂机游戏(完整实战记录)
mcp·ai游戏开发·workbuddy·taptap制造·放置挂机游戏·零代码游戏
冬奇Lab1 天前
MCP 系列(01):MCP 是什么——为什么 Function Calling 不够
人工智能·llm·mcp
Esaka_Forever1 天前
MCP Prompt(模型上下文协议 提示模板)完整详解
mcp
元Y亨H1 天前
Model Context Protocol (MCP) 全方位解析
llm·mcp