写Agent还要重复封装工具?一套MCP多服务方案,3个能力让AI自动查地图、读写文件、操控浏览器

前言

做AI Agent开发最折磨人的是什么? 每接入一类外部能力,就要写一套胶水代码、适配一套接口、处理不同返回格式。

想让AI查地图,单独封装高德接口;想读写本地文件,重新写文件操作函数;想控制浏览器,又要对接Chrome调试协议。 代码冗余、维护成本爆炸,换个大模型还要重新适配一遍工具调用逻辑。

直到我用上 MCP(Model Context Protocol) 协议,彻底解决这个痛点。

读完本文你能学到:

  1. MCP协议核心价值,为什么它是AI工具统一标准
  2. Node.js+LangChain完整可运行代码,同时对接3种MCP服务
  3. 真实实战场景:AI自主查询酒店、生成文档、打开浏览器分页展示图片
  4. 开发中高频踩坑点与修复方案
  5. 多MCP服务协同Agent完整执行流程

一、先搞懂:MCP到底是什么?

MCP全称模型上下文协议,是AI领域标准化工具通信协议,业内俗称AI万能USB插头

传统工具接入痛点

  • 每个第三方服务、本地能力都要单独写适配层
  • 返回格式不统一,每次调用都要手动做数据兼容
  • 切换大模型、更换工具时,大量代码需要重构

MCP带来的改变

所有工具统一封装成MCP Server,通过stdio/HTTP两种方式通信。 客户端只需要一次配置,就能自动读取全部工具,交给LLM自由调用。

本文实战用到3套成熟MCP服务:

  1. amap-maps-streamableHTTP:远程HTTP服务,高德地图,查位置、酒店、路线
  2. filesystem:本地子进程服务,读写本地文件、创建目录、生成md文档
  3. chrome-devtools:本地子进程服务,操控Chrome浏览器,新开标签、修改页面标题、打开图片链接

二、项目前置依赖安装

新建项目,安装全部依赖包,复制执行:

bash 复制代码
npm install dotenv @langchain/mcp-adapters @langchain/openai chalk langchain

.env环境变量配置

项目根目录新建.env文件,填入密钥:

env 复制代码
DEEPSEEK_API_KEY=你的DeepSeek密钥
AMAP_KEY=你的高德地图key

三、完整可运行实战代码 mcp-test.js

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

// 初始化DeepSeek大模型,兼容OpenAI调用格式
const model = new ChatOpenAI({
  modelName:'deepseek-v4-pro',
  apiKey: process.env.DEEPSEEK_API_KEY,
  temperature: 0, // 设为0保证工具调用稳定,不随机发散
  configuration: {
    baseURL: 'https://api.deepseek.com/v1',
  },
});

// 多MCP服务统一客户端配置
const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        // 高德地图远程MCP服务,HTTP流式传输
        'amap-maps-streamableHTTP': {
            "url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_KEY}`
        },
        // 本地文件系统MCP,npx启动子进程,限定访问目录
        'filesystem': {
            command: 'npx',
            args: [
                '-y',
                '@modelcontextprotocol/server-filesystem',
                'C:/Users/Administrator/Desktop/ai_doubao_ysw/ai/agent_in_action/remote-mcp'
            ]
        },
        // Chrome浏览器控制MCP,本地调试端口9222
        'chrome-devtools': {
            command: 'npx',
            args: [
                '-y',
                'chrome-devtools-mcp@latest',
            ]
        }
    }
});

// 自动拉取全部MCP服务暴露的工具
const tools = await mcpClient.getTools();
// 将所有工具绑定到大模型,模型自动识别可用操作
const modelWithTools = model.bindTools(tools);

/**
 * Agent循环执行核心函数
 * @param {string} query 用户任务指令
 * @param {number} maxIterations 最大迭代轮次,防止死循环
 * @returns {string} 最终执行结果
 */
async function runAgentWithTools(query, maxIterations = 30) {
    const messages = [
        new HumanMessage(query)
    ];
    // 循环执行:模型思考→调用工具→接收结果→再次思考
    for (let i = 0; i < maxIterations; i++) {
        console.log(chalk.bgGreen(`第${i+1}轮迭代 `));
        const response = await modelWithTools.invoke(messages);
        messages.push(response);

        // 无工具调用,任务结束,返回最终回答
        if (!response.tool_calls || response.tool_calls.length === 0) {
            console.log(chalk.bgRed(`AI 最终回答: ${response.content}`));
            return response.content
        }

        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;
                // 兼容MCP两种返回格式:纯字符串 / {text: "内容"} 对象
                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
                }));
            }
        }
    }
    // 达到最大迭代次数,返回最后一条AI输出
    return messages[messages.length-1].content;
}

// 实战任务:查询北京南站最近3家酒店、生成文档、浏览器分页打开酒店图片并修改标签标题
await runAgentWithTools("北京南站附近的酒店,最近的 3 个酒店,拿到酒店图片,打开浏览器,每个tab一个url展示,并且把页面标题改为酒店名,同时将酒店信息、步行/驾车路线保存为本地md文档");

// 关闭所有MCP进程连接
await mcpClient.close();

四、实战场景完整执行效果

需求拆解

  1. 调用高德MCP,定位北京南站,筛选距离最近3家酒店,获取坐标、地址、评分、图片链接、步行/驾车路线
  2. 调用FileSystem MCP,自动生成北京南站附近酒店及路线指南.md保存全部数据
  3. 调用Chrome DevTools MCP,新开3个浏览器标签,分别加载酒店图片,修改每个Tab页面标题为对应酒店名称

生成文档核心内容节选

北京南站附近酒店及路线指南

📍 北京南站 | 坐标:116.378059, 39.867679 | 北京市丰台区


🏨 推荐酒店概览

序号 酒店名称 评分 步行距离 驾车距离
1 汉庭酒店(北京南站北广场店) ⭐4.4 约403米 约1.2公里
2 海友酒店(北京南站南广场店) ⭐4.4 约1.6公里 约1.8公里
3 桔子酒店(北京南站店) ⭐4.6 约1.5公里 约1.6公里

酒店路线详情

每家酒店单独区块,包含完整步行分步指引、驾车路线、商圈信息,最后附带综合对比表格与入住推荐建议。

浏览器执行动作

Agent拿到每家酒店图片URL后,自动调用Chrome工具:

  1. 新建独立Tab页
  2. 跳转图片链接
  3. 修改页面document.title为酒店全称
  4. 3个酒店对应3个独立浏览器标签

五、开发高频踩坑提醒

坑1:Chrome MCP启动失败

报错:无法连接Chrome调试端口 解决:启动Chrome时开启远程调试端口

bash 复制代码
chrome --remote-debugging-port=9222

必须先打开带调试端口的浏览器,再运行脚本。

坑2:FileSystem无权限读写文件

报错:文件夹访问拒绝 解决 :npx参数里的目录路径必须是绝对路径,Windows路径分隔符统一用/,不要用\

坑3:工具调用频繁失效、模型乱输出

解决 :LLM配置temperature:0,降低随机度,保证工具调用逻辑稳定;最大迭代次数建议设置20~30,避免多轮工具调用中断。

坑4:MCP返回数据解析报错

部分MCP工具返回对象带text字段,部分直接返回纯字符串,代码中做了双重兼容,不要删除类型判断逻辑。

坑5:脚本运行结束进程残留

代码末尾必须执行await mcpClient.close(),否则npx启动的子进程会常驻后台占用端口。

六、MCP核心优势总结

  1. 统一工具标准:高德、文件、浏览器三类能力一套客户端管理,无需单独写接口适配
  2. 可复用生态:任何人开发MCP Server,直接接入项目,不用重构Agent逻辑
  3. 本地/远程双模式:高德HTTP远程服务、文件/浏览器本地子进程服务同时兼容
  4. Agent自动编排:大模型自主判断何时调用地图、何时写文件、何时操控浏览器,不用手动拆分任务
  5. 低维护成本:新增工具仅需在mcpServers配置新增节点,其余代码完全不用改动
相关推荐
GuWenyue1 小时前
90%AI新手不会调参!LangChain双模型流水线实战,一套代码兼顾严谨+创意
人工智能
光锥智能3 小时前
WAIC亮点|兼具泛化能力与作业效率的极智嘉机器人天团
人工智能·机器人
阳光是sunny3 小时前
LangGraph中的Reducer是什么
前端·人工智能·后端
甲维斯3 小时前
Kimi K3 重构10000行单文件屎山代码!
人工智能
阳光是sunny3 小时前
从链到图:LangGraph 入门基础全解析
前端·人工智能·后端
新知图书3 小时前
11.3 详细实现与核心配置(作业批改智能体开发)
人工智能·agent·ai agent·智能体·扣子
深度研习笔记4 小时前
OpenCV工业视觉实战11|多线程解耦+视频流稳流+推理加速,彻底解决卡顿阻塞,实现毫秒级工业实时检测
人工智能·opencv·计算机视觉
delishcomcn4 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹4 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试