大模型不仅智商爆表还想操纵你的电脑?那就给它配个 MCP 协议试试!

你是否也遇到过这种情况:对着 ChatGPT 或 DeepSeek 咆哮了半天"帮我查一下昨天数据库里新增的用户",或者"帮我把这个网页截个图",AI 却只能委屈地敲出一行:"对不起,作为一个语言模型,我无法访问您的本地数据,也无法操控您的浏览器。"

这时候,你恨不得顺着网线过去给它两拳。

传统的 AI 模型就像是一个智商爆表但"断网、断电、无手无脚"的闭关学者 。为了让它能干活,我们以前会给它写 Tools(工具回调),但每个项目的工具写法千奇百怪。

直到 MCP(Model Context Protocol,模型上下文协议) 诞生,游戏规则变了。今天,我们就来扒开 MCP 的神秘面纱,看看如何用它和 LangChain 强强联手,把一个"只会聊天"的 AI 变成能查数据库、能控浏览器、能读写文件的"超级特工(Agent)"。

一、 什么是 MCP?(小白也能听懂的"普通话"解释)

在正式敲代码之前,我们先搞懂什么是 MCP。别慌,我们不用复杂的网络术语。

我们可以把 AI 模型想象成一部智能手机的操作系统(比如 iOS 或 Android)。

  • 在没有统一协议的年代:每家硬件厂商(高德地图、你的本地文件、Chrome 浏览器)为了连接手机,都得自己焊一套独特的接口。手机系统想调用它们?对不起,得针对每一个硬件单独写几千行兼容代码。
  • 有了 MCP 协议之后 :它就像是 USB-C 接口规范。MCP 协议规定了统一的插口标准。任何厂商(MCP Server)只要做成 USB-C 接口,就能直接插到支持 MCP 的客户端(MCP Client,比如 Cursor 或我们的 LangChain 脚本)上。

核心三要素

  1. MCP Client(客户端) :相当于大脑的主控制器。它负责调配 AI 模型,并向各种工具发出指令。

  2. MCP Server(服务端) :相当于各种各样的"外设器官"。它守着自己的"一亩三分地"(比如数据库、本地文件系统系统、浏览器驱动),等着 Client 传指令过来,然后执行具体操作。

  3. 通信通道(Transport) :Client 和 Server 之间怎么说话?通常有两种方式:

    • Stdio(标准输入输出) :就像两个进程在同一台电脑里用"悄悄话"管道通信,速度极快,适合本地扩展。
    • HTTP/SSE(网络传输) :通过网络请求远端服务,适合跨服务器调用。

有了这个大前提,任何人开发的 MCP 工具,都能无缝接入任何支持 MCP 的 AI 系统中。这就是它的魅力所在。

二、 动手建一个自己的"秘密基地":自定义 MCP Server

光说不练假把式。我们先用 Node.js 亲手撸一个本地的 MCP Server。这个 Server 将扮演一个"档案管理员",手里握着一份假数据,并且提供工具(Tools)静态资源(Resources)

1. 基础骨架与数据准备

首先,我们引入官方的 SDK,并初始化一个 MCP 服务端实例。为了让数据校验更严谨,我们引入 zod 库来做入参检查。

JavaScript

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

// 创建 Server 实例,给它起个响亮的名字
const server = new McpServer({
  name: 'my-mcp-server',
  version: '1.0.0'
});

2. 注册动态工具:query_user

有了服务,我们得给 AI 提供"抓手"。我们注册一个名为 query_user 的工具。当 AI 发现人类想查用户时,就会把"用户ID"传给这个工具。

JavaScript

javascript 复制代码
server.registerTool('query_user', {
  description: '查询数据库中的用户信息。输入用户ID, 返回该用户的详细信息(姓名、邮箱、角色)',
  inputSchema: {
    // 限制入参必须是字符串,并用 describe 贴心地告诉 AI 应该传什么格式
    userId: z.string().describe('用户ID, 例如:001, 002, 003')
  }
}, async ({ userId }) => {
  const user = database.users[userId];
  
  // 如果找不到用户,别玩失踪,礼貌地告诉 AI
  if (!user) {
    return {
      content: [{
        type: 'text',
        text: `用户 ID ${userId} 不存在。可用的ID: 001, 002, 003`
      }]
    };
  }
  
  // 找到了,打包成标准格式扔给 AI
  return {
    content: [{
      type: 'text',
      text: `用户 ${user.id} 的信息是:姓名: ${user.name}, 邮箱: ${user.email}, 角色: ${user.role}`
    }]
  };
});

3. 注册静态资源:使用指南

除了能动态执行代码的"工具",MCP 还支持"资源(Resource)"。资源就像是摆在架子上的书,AI 可以走过去翻阅它。比如,我们给它准备一本《使用指南》。

JavaScript

php 复制代码
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 中通过自然语言对话,Cursor 会自动调用相应工具。
        `
      }]
    };
  }
);

4. 开启通信管道

最后一步,让这个 Server 跑起来。我们选择最基础、最稳健的 Stdio(标准输入输出)方式。

JavaScript

csharp 复制代码
// 采用跨进程的标准输入输出通信方式
const transport = new StdioServerTransport();
await server.connect(transport);

至此,我们的本地 MCP Server 就写好了。它就像一个守门人,静静等待着主进程的召唤。

三、 组装"大黄蜂":在 LangChain 中驱动多 MCP 核心

有了服务端,接下来就是重头戏:编写客户端,用 LangChain 驱动 AI,让它自己学会查阅资源、调用工具。

我们要用到 @langchain/mcp-adapters 库中的 MultiServerMCPClient。顾名思义,它能同时粘合多个不同的 MCP 模块

1. 初始化 AI 模型与 MCP 客户端

我们先配置好我们的大脑(大模型)以及手脚(MCP 客户端)。

JavaScript

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. 初始化大语言模型(这里以强大的 DeepSeek 为例)
const model = new ChatOpenAI({
  modelName: 'deepseek-v4-pro',
  apiKey: process.env.DEEPSEEK_API_KEY,
  temperature: 0, // 搞工程,我们要让 AI 稳重,拒绝瞎编,所以创造性设为 0
  configuration: {
    baseURL: 'https://api.deepseek.com/v1',
  },
});

// 2. 组装我们的 MCP 客户端,把刚刚写的本地 Server 配进去
const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: 'node', // 用 node 命令启动
      args: ['D:/Workspace/xld_ai/ai/agent_in_action/mcp-demo/src/my-mcp-server.mjs'] // 你的文件绝对路径
    }
  }
});

2. 抽取资源,喂给 AI 当背景知识

还记得我们在 Server 里写的《使用指南》资源吗?在对话开始前,Client 可以先把这些静态资源读出来,塞进 AI 的 SystemMessage(系统提示词)里,让 AI 提前对当前环境有个认知。

JavaScript

ini 复制代码
// 获取所有注册的工具和资源
const tools = await mcpClient.getTools();
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(chalk.gray('--- 读取到的组件资源 ---'));
console.log(resourceContent);

// 将工具绑定到大模型上,让模型具备"选择工具"的能力
const modelWithTools = model.bindTools(tools);

3. 核心:编写 Agent 的"思考-执行"循环循环(ReAct 架构)

AI 是怎么用工具的?其实是一个循环过程:

  1. 思考:用户问了一个问题,我现有的知识答不上来,我应该用哪个工具?
  2. 决策 :决定调用 query_user 工具,参数是 002
  3. 执行 :代码拦截到 AI 的决策,去跑真正的工具代码,拿到结果(如:"光光,gg@qq.com")。
  4. 反馈:把结果再喂回给 AI,AI 恍然大悟,最后组织语言回答人类。

我们用一个 for 循环来帮 AI 实现这个自主思考的闭环:

JavaScript

javascript 复制代码
async function runAgentWithTools(query, maxIterations = 30) {
  // 初始化消息队列,把资源指南作为系统背景,加上用户的提问
  const messages = [
    new SystemMessage(resourceContent),
    new HumanMessage(query)
  ];

  for (let i = 0; i < maxIterations; i++) {
    console.log(chalk.bgGreen(`正在等待AI思考, 第${i + 1}轮....`));
    
    // 让 AI 来看当前的所有对话记录,并作出回应
    const response = await modelWithTools.invoke(messages);
    messages.push(response); // 把 AI 的回应先记录下来

    // 关键判断:如果 AI 觉得不需要调用工具了,说明它已经得出最终答案了,直接退出
    if (!response.tool_calls || response.tool_calls.length === 0) {
      console.log(chalk.green(`\nAI 最终回复:\n${response.content}`));
      return response.content;
    }

    // 来到这里,说明 AI 喊话了:"我要用工具!"
    console.log(chalk.bgBlue(`检测到 ${response.tool_calls.length} 个工具调用`));
    console.log(chalk.blue(`工具调用: ${response.tool_calls.map(t => t.name).join(', ')}`));

    // 遍历 AI 想要调用的每一个工具
    for (const toolCall of response.tool_calls) {
      // 从我们客户端维护的 tools 列表里找到对应的那项
      const foundTool = tools.find(t => t.name === toolCall.name);
      
      if (foundTool) {
        // 真正去执行工具,传入 AI 帮我们分析好的参数
        const toolResult = await foundTool.invoke(toolCall.args);
        
        // 【核心注意点】:一定要带上 tool_call_id,这相当于给回复打上标签,AI 才知道这个数据对应的是哪个请求
        messages.push(new ToolMessage({
          content: typeof toolResult === 'string' ? toolResult : (toolResult.text || JSON.stringify(toolResult)),
          tool_call_id: toolCall.id
        }));
      }
    }
  }
  
  return messages[messages.length - 1].content;
}

// 狂奔吧,Agent!
await runAgentWithTools('查一下用户002的信息,顺便看看MCP Server的使用指南是什么?');

// 大功告成后,别忘了优雅地关闭客户端,释放子进程资源
await mcpClient.close();

四、 想象力炸裂:多 Server 联动的"乐高式"AI 工作流

如果 MCP 只能查个本地假数据,那真是格局小了。MCP 真正的恐怖之处在于生态复用

既然它是"标准接口",这意味着我们可以把"高德地图"、"Chrome 浏览器驱动"、"本地文件系统"像搭乐高一样一次性全插在同一个 Client 上。

来看看下面这个疯狂的进阶配置:

JavaScript

arduino 复制代码
const mcpClient = new MultiServerMCPClient({
  // 1. 远程 HTTP 接入高德地图 MCP:查位置、查路线
  'amap-maps': {
    url: 'https://mcp.amap.com/mcp?key=YOUR_AMAP_KEY'
  },
  
  // 2. 操控 Chrome 浏览器:打开网页、点击元素、截图、爬虫
  'chrome-devtools': {
    command: 'npx',
    args: ['-y', 'chrome-devtools-mcp@latest']
  },
  
  // 3. 读写本地文件系统:直接写代码、存文件
  'filesystem': {
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-filesystem', 'D:/Workspace/xld_ai']
  }
});

当你把上面这堆东西绑好,再丢给 Agent 一句极其复杂的任务:

"帮我拿到东华理工大学抚州校区的地图图片,然后自动打开我的浏览器,展示这张校区图片。"

AI 看到这个任务,脑子里会自动拆解出完美的交响乐章:

  1. 调用 amap-maps 工具去搜索"东华理工大学抚州校区"的地理位置和图片链接。
  2. 调用 filesystem 工具在本地创建一个工作目录,把图片数据落盘。
  3. 调用 chrome-devtools 工具自动拉起一个真的 Chrome 浏览器,跳转到对应的本地文件或者图片地址上展示给你看。

整个过程不需要你为它定制编写一行有关"如何截图、如何开浏览器"的底层业务代码,你只是引入了三个现成的、标准的 MCP 服务。

五、 避坑指南与总结

在玩转 MCP 的路上,有几个底层的技术细节需要刻在DNA里:

  • tool_call_id 绝对不能丢 :在多轮对话中,AI 发出了调用工具的请求,当你把工具结果返回给 AI 时,必须用 ToolMessage 并在属性里带上 tool_call_id。这就跟去饭店吃饭拿小票取餐一样,对不上号 AI 就会陷入逻辑混乱。
  • 及时 close() 释放资源 :使用 StdioServerTransport 时,客户端实质上是通过 Node.js 的 child_process 启动了子进程来和 Server 通信的。如果不手动调用 await mcpClient.close(),这些子进程和通信通道就会一直挂在后台,导致你的脚本死活不退出,白白榨干你的 CPU 资源。

MCP 协议的出现,划清了"大模型负责思考"与"周边生态负责执行"的界线。作为开发者的我们,不用再重复造轮子去写各种杂乱的 API 适配器了。只要拥抱 MCP,世界上的所有工具,都将在一夜之间成为你 Agent 的不二臣子!

相关推荐
一键生成网站1 小时前
政企项目AI数据大屏生成工具怎么选信创合规私有化部署指南
人工智能·
阿牛哥_GX1 小时前
错误处理与运维监控:让自动化脚本永不静默失败(完结篇)
人工智能
shuoyuanAI1 小时前
张家口专业的aigeo优化品牌找哪家
大数据·人工智能·python
甜包AI笔记1 小时前
Loop Engineer:让 AI 在工程闭环里自动推进任务
人工智能
killerbasd1 小时前
总结 7。10
人工智能·算法·机器学习
GlobalInfo1 小时前
行业报告解读:可穿戴电子织物市场扩张背后,有哪些新机遇?
人工智能·物联网·microsoft
林间码客1 小时前
RAG系统评估指南:从入门到实践
人工智能·算法·机器学习
菊链诗腾1 小时前
部署大模型8GB显存128K大模型调优实战
人工智能·调优·本地大模型