🔌 给 AI 装上“三头六臂”!实战大模型接入第三方 MCP 全攻略

0 阅读3分钟

大家好!我是你们的 AI 炼丹师。👋

在前几期的文章里,我们聊了 MCP(Model Context Protocol)的概念,甚至还手搓了一个简单的本地 MCP Server。很多同学在后台私信:“道理我都懂,但每次都要自己写 Server 还是很累啊!有没有现成的轮子可以用?”

有!必须有! 🚗

MCP 的生态正在以爆炸般的速度增长。今天,我们不再纸上谈兵,直接上代码实战!我们要干一件很酷的事情:在一个 Agent 里同时集成高德地图、本地文件系统、Chrome 浏览器控制这三大神级能力,而且——一行工具逻辑的代码都不用我们写!

准备好了吗?硬核实战,Starting Now! 🚀


🏗️ 核心基建:LangChain 的 MCP 适配器

首先,我们的“地基”依然是 LangChain。为了让 LangChain 能听懂 MCP 协议,我们需要引入那个熟悉的“翻译官”。

import { MultiServerMCPClient } from '@langchain/mcp-adapters';

🔍 深度解析MultiServerMCPClient 是我们的核心指挥官。它的厉害之处在于“Multi”——它允许我们同时配置多个 MCP Server。不管你是跑在本地的 Node 进程,还是远在天边的 HTTP 服务,它都能统一管理,把它们提供的 Tools 聚合在一起,打包送给 LLM。


🎯 今日挑战:Agent 的“铁人三项”

为了验证 MCP 的强大,我们给 AI 布置了三个层层递进的任务:

  1. 任务一(问路):查询北京南站附近的酒店及路线。(考验地理信息获取能力)
  2. 任务二(文秘):规划路线并生成 Markdown 文档保存到本地。(考验文件系统读写能力)
  3. 任务三(上网):拿到酒店图片,打开浏览器展示,并修改标签页标题。(考验浏览器控制能力)

这要是在以前,我得写三个复杂的 Tool 类,还得调试半天 API。但今天,我们用 MCP 来秒杀它们。


🗺️ 第一关:接入高德地图 (Remote MCP)

先来解决地图问题。我们不需要自己去申请高德 API Key、看文档、写 HTTP 请求。我们直接使用高德提供的 MCP 服务。

1. 配置远程 MCP

"amap-maps": {
    "url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`
},

🔍 硬核讲解: 大家发现了吗?这个配置和我们昨天的本地配置不一样!

  • 本地 MCP:需要指定 command (如 node) 和 args (脚本路径),Client 负责启动和管理进程。
  • 远程 MCP:只需要一个 url!Client 会通过 SSE (Server-Sent Events) 等标准协议与远程服务建立连接。

只要配置好这个 URL,你的 Agent 瞬间就拥有了查询 POI(兴趣点)、路径规划、逆地理编码等全套高德地图能力。这就是云端赋能的魅力。☁️

image.png 可以看到工具非常丰富。

2. 更加健壮的 ToolResult 处理

接入了高德 MCP 后,Agent 就可以开始工作了。但在处理工具返回结果时,我们需要格外注意。

重点看这里:

const toolResult = await foundTool.invoke(toolCall.args);
let contentStr;
// 🛡️ 防御性编程:解包 ToolResult
if (typeof toolResult === 'string') {
    contentStr = toolResult;
} else if (toolResult && toolResult.text) {
    contentStr = toolResult.text;
}

🔍 为什么要这么写? MCP 协议规定,Tool 的返回结果通常是一个包含 content 数组的对象,每一项可能有 type: 'text'text 字段。

  • 有的 SDK 封装得好,直接给你返回字符串。
  • 有的比较原始,返回的是 JSON 对象 { content: [...] } 或者 { text: "..." }。 所以,为了防止 Agent 因为看不懂结果而报错,我们需要做一个简单的“拆包”处理,把真正的内容拿出来喂给 LLM。

📂 第二关:接入文件系统 (FileSystem MCP)

第二个任务要求把结果保存为文件。以前我们要写 fs.writeFileSync,还要担心 AI 会不会把我的 C 盘格机了。😱

现在,我们使用 MCP 官方提供的 FileSystem Server

1. 使用 npx 即时加载

// mcp 官网提供
"filesystem": {
    "command": "npx", // 🔥 亮点来了
    "args": [
       "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:\\Users\\MR\\Desktop\\workspace\\lesson_jp\\ai\\agent\\mcp_in_action\\mcp-test" // 操作范围
    ]
},

🔍 硬核讲解: 这里我们用到了 npx。这意味着我们甚至不需要在项目中 npm install 这个包!

  • command: "npx": 告诉 Client,启动服务的方式是运行 npx 命令。
  • "-y": 必考题! 它的意思是 "Yes"。当你第一次用 npx 运行某个包时,它通常会问你“是否安装... (y/n)”。但在 MCP 的自动化流程里,没人去按那个 'y',所以必须加上这个参数自动确认,否则进程会卡死。
  • @modelcontextprotocol/server-filesystem: 这是官方标准包的名字。

2. 安全沙箱机制 🛡️

注意看参数的最后一行: "C:\\...\\mcp-test"

这非常关键! 这是文件系统的挂载点(Mount Point)。 MCP 的文件系统服务是受限的。我们显式地告诉它:“你只能在这个文件夹里读写文件”。 这样,无论 LLM 怎么发疯,它都绝对无法触碰到这个文件夹以外的任何文件。这就是 MCP 在架构层面提供的安全保障。

image.png

3. 成果展示

看看 AI 生成的文档:

# 北京南站附近酒店及路线指南
...
### 1. 米家青年酒店(北京南站店)
- **步行路线**: 
  - 沿南站幸福路向西步行98米右转...
...
> Note: All coordinates and route information are based on high-precision GPS data from Amap services.

完美!数据来自高德 MCP,写入通过 FileSystem MCP,配合得天衣无缝。✨


🌐 第三关:掌控浏览器 (Chrome DevTools MCP)

任务升级!我们要让 AI 打开浏览器给我们看图片。这需要更高级的交互。我们将引入 Google 官方的 Chrome DevTools MCP

1. 配置浏览器控制台

"chrome-devtools": {
    "command": "npx",
    "args": [
        "-y",
        "chrome-devtools-mcp@latest" // 使用最新版本
    ]
}

🔍 玩法解析: 这里同样使用了 npx-ychrome-devtools-mcp 是一个非常强大的工具,它允许 Agent 通过 CDP (Chrome DevTools Protocol) 协议控制你的浏览器。

  • 它可以打开 Tab。
  • 它可以截图。
  • 它可以执行 JS 脚本。
  • 它可以读取控制台日志。

2. 执行效果

当我们下达任务:

“北京南站附近的三个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片...并且把那个页面标题改为酒店名”

AI 会怎么做?

  1. 调用 高德 MCP 搜索酒店,拿到图片 URL。
  2. 调用 Chrome MCPopen_tab 工具打开 URL。
  3. 调用 Chrome MCPevaluate_javascript 工具修改 document.title

你将亲眼看到浏览器窗口一个个弹出,页面自动跳转,标题自动变更。这就是 Agent Native 的体验!🤖🖥️


💡 总结与思考

今天这堂实战课,我们没有写任何具体的 Tool 逻辑代码,仅仅通过配置 JSON,就赋予了 Agent 地图搜索、文件读写、浏览器控制三大神技。

这给我们带来了什么启示?

  1. Don't Repeat Yourself (DRY): 不要重复造轮子。社区里有现成的 MCP Server,拿来用就是了。
  2. 组合式创新: 我们的工作重心从“怎么写工具”转移到了“怎么组合工具”。Agent 的能力边界取决于你接入了多少 MCP Server。
  3. 异构融合: 高德(云端)、文件系统(本地 Node)、浏览器(本地 App),通过 MCP 协议,它们在同一个 Agent 的上下文中和谐共存。

未来已来。不需要等到明天,现在就去挑选几个好用的 MCP Server,给你的 Agent 装备上吧!

如果觉得文章有用,记得点赞收藏,咱们下期见!👋


附:环境依赖

  • Node.js 环境
  • @langchain/mcp-adapters
  • @langchain/openai
  • 环境变量 AMAP_MAPS_API_KEY (高德地图 Key)
  • 环境变量 OPENAI_API_KEY

完整代码

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

const model = new ChatOpenAI({
    modelName: process.env.MODEL_NAME,
    apiKey: process.env.OPENAI_API_KEY,
    baseUrl: process.env.OPENAI_BASE_URL
});

const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        "amap-maps": {
            "url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`
        },
        // mcp 官网提供
        "filesystem": {
            "command": "npx",
            "args": [
               "-y",
                "@modelcontextprotocol/server-filesystem",
                "C:\\Users\\MR\\Desktop\\workspace\\lesson_jp\\ai\\agent\\mcp_in_action\\mcp-test" // 操作范围
            ]
        },
        "chrome-devtools": {
            "command": "npx",
            "args": [
                "-y",
                "chrome-devtools-mcp@latest"
            ]
        }
    }
})

const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);

async function runAgentWithTools(query, maxIterations = 30) {
    const messages = [
        new HumanMessage(query)
    ];
    for (let i = 0; i < maxIterations; i++) {
        console.log(chalk.bgGreen('⏳正在等待AI思考...'));
        const response = await modelWithTools.invoke(messages);
        messages.push(response);

        if (!response.tool_calls || response.tool_calls.length === 0) {
            console.log(`\n AI 最终回复:\n ${response.content}\n`);
            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);
                let contentStr;
                if (typeof toolResult === 'string') {
                    contentStr = toolResult;
                } else if (toolResult && toolResult.text) {
                    contentStr = toolResult.text;
                }
                messages.push(new ToolMessage({
                    content: contentStr,
                    tool_call_id: toolCall.id
                }));
            }
        }
    }
    return messages[messages.length - 1].content;
}

// await runAgentWithTools('北京南站附近的酒店,以及去的路线');
// await runAgentWithTools(`北京南站附近的2个酒店,以及去的路线,
//     路线规划生成文档保存到当前目录的一个 md 文件`);
await runAgentWithTools(`
    北京南站附近的三个酒店,拿到酒店图片,展开浏览器,展示每个酒店的图片
    每个tap一个url展示,并且把那个页面标题改为酒店名    
`)
await mcpClient.close();