远程MCP实战:高德地图 + Chrome DevTools + File System 三大 Server 联动打造 AI 超级 Agent

远程MCP实战:高德地图 + Chrome DevTools + File System 三大 Server 联动打造 AI 超级 Agent

上一篇文章我们用 MultiServerMCPClient 连接了自建的本地 MCP Server。但 MCP 的真正威力在于:任何人都可以开发 MCP Server,你直接拿来用就行。本文将带你一次性接入三个远程/社区 MCP Server ------ 高德地图、Chrome DevTools、File System,让 AI Agent 能查位置、能控制浏览器、能写文件,完成真正的复杂工作流。

前置知识

  • 理解 MCP 协议基本概念(Server / Client / Host 三层架构)
  • 了解 MultiServerMCPClient 的基本用法
  • 了解 LangChain bindTools + ReAct 循环机制

如果还不熟悉,建议先阅读:


一、为什么需要远程 MCP?

在之前的文章中,我们自己写了一个 my-mcp-server.mjs,通过 stdio 与 Agent 通信。但现实中,绝大多数工具你不需要自己写

MCP 的生态优势

arduino 复制代码
传统工具调用方式:
  Agent → 自己写代码集成 Tool → 耦合、难复用

MCP 标准化方式:
  Agent → MCP 协议 → 任何人开发的 MCP Server → 即插即用

MCP 本质上就是 Tool + 进程封装 + 标准化通信 。通过 stdioHTTP,Agent 可以跨进程、跨语言地调用任意 MCP Server 提供的工具和资源。

三大实战场景

MCP Server 通信方式 能力
高德地图 HTTP (远程) 位置查询、POI 搜索、路线规划、酒店/餐厅推荐
Chrome DevTools stdio (npx) 控制浏览器:打开网页、点击元素、截图、修改标题
File System stdio (npx) 读写文件、创建目录、管理本地文件系统

这三个 Server 联动起来,Agent 就能完成这样的复杂工作流:

"查北京南站附近的酒店,拿到酒店图片,打开浏览器展示每个酒店的图片(每个 tab 一个 URL),并把页面标题改为酒店名,最后把路线规划保存到本地 md 文件"


二、MCP Server 接入方式对比

2.1 HTTP 远程接入(高德地图)

高德地图提供了官方的 MCP Server,通过 HTTP 直接连接,无需安装任何依赖

javascript 复制代码
'amap-maps-streamableHTTP': {
    "url": "https://mcp.amap.com/mcp?key=你的高德Key"
}

这是最简洁的接入方式。高德 MCP Server 暴露在公网,Agent 通过 HTTP 请求与之通信,获取地图、POI、路线规划等能力。

2.2 stdio 本地接入(File System + Chrome DevTools)

社区 MCP Server 通常通过 npx 直接运行,无需手动 clone 仓库或全局安装:

javascript 复制代码
// File System MCP - 读写本地文件
'filesystem': {
    command: 'npx',
    args: [
        '-y',
        '@modelcontextprotocol/server-filesystem',
        // 允许访问的文件夹(安全限制)
        '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-mcp'
    ]
}

// Chrome DevTools MCP - 控制浏览器
'chrome-devtools': {
    command: 'npx',
    args: [
        '-y',
        'chrome-devtools-mcp@latest',
    ]
}

注意:Chrome DevTools MCP 需要提前以远程调试模式启动 Chrome:

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

通信方式对比图

arduino 复制代码
┌─────────────────────────────────────────────────────┐
│                   Agent (Host)                       │
│            MultiServerMCPClient                      │
│                                                      │
│   ┌──────────────┐  ┌──────────────┐  ┌───────────┐  │
│   │ HTTP Client  │  │ stdio Child  │  │ stdio     │  │
│   │              │  │   Process    │  │  Child    │  │
│   └──────┬───────┘  └──────┬───────┘  └─────┬─────┘  │
│          │                 │                 │         │
└──────────┼─────────────────┼─────────────────┼─────────┘
           │                 │                 │
     ┌─────▼─────┐   ┌───────▼───────┐  ┌──────▼──────┐
     │  高德地图   │   │  File System  │  │   Chrome    │
     │  MCP Server│   │  MCP Server   │  │  DevTools   │
     │  (远程HTTP) │   │  (本地stdio)  │  │  MCP Server │
     └───────────┘   └───────────────┘  │  (本地stdio) │
                                       └─────────────┘

三、完整代码:三 Server 联动 Agent

3.1 项目初始化

bash 复制代码
mkdir remote-mcp && cd remote-mcp
npm init -y
npm install @langchain/mcp-adapters @langchain/openai @langchain/core chalk dotenv

创建 .env 文件:

env 复制代码
DEEPSEEK_API_KEY=sk-your-deepseek-key

3.2 核心 Agent 代码(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. 配置多个 MCP Server ==========
const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        // 远程 HTTP Server - 高德地图
        'amap-maps-streamableHTTP': {
            "url": "https://mcp.amap.com/mcp?key=你的高德Key"
        },
        // 本地 stdio Server - 文件系统
        'filesystem': {
            command: 'npx',
            args: [
                '-y',
                '@modelcontextprotocol/server-filesystem',
                '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-mcp'
            ]
        },
        // 本地 stdio Server - Chrome DevTools
        // 需要先启动: chrome --remote-debugging-port=9222
        'chrome-devtools': {
            command: 'npx',
            args: [
                '-y',
                'chrome-devtools-mcp@latest',
            ]
        }
    }
});

// ========== 3. 自动获取所有 Server 的工具 ==========
const tools = await mcpClient.getTools();
// tools 中现在包含了高德地图、File System、Chrome DevTools 的所有工具
console.log(`已加载 ${tools.length} 个工具:`);
tools.forEach(t => console.log(`  - ${t.name}`));

const modelWithTools = model.bindTools(tools);

// ========== 4. ReAct 循环 ==========
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);

        // 如果 AI 不再调用工具,说明已经得出最终答案
        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);

                // 兼容不同 MCP Server 的返回格式
                let contentStr;
                if (typeof toolResult === 'string') {
                    contentStr = toolResult;
                } else if (toolResult && toolResult.text) {
                    // File System 等 Server 可能返回 { text: "..." } 对象
                    contentStr = toolResult.text;
                }

                // 必须带 tool_call_id,否则 LLM 无法关联
                messages.push(new ToolMessage({
                    content: contentStr,
                    tool_call_id: toolCall.id
                }));
            }
        }
    }

    // 达到最大迭代次数,返回最后一条消息
    return messages[messages.length - 1].content;
}

// ========== 5. 执行任务 ==========
await runAgentWithTools(
    "北京南站附近的酒店,最近的 3 个酒店," +
    "拿到酒店图片,打开浏览器,展示每个酒店的图片," +
    "每个 tab 一个 url 展示,并且把那个页面标题改为酒店名"
);

// ========== 6. 关闭所有连接 ==========
await mcpClient.close();

3.3 代码执行流程

arduino 复制代码
用户提问:"北京南站附近的酒店..."
    │
    ▼
第 1 轮:AI 调用 amap_search_nearby → 高德地图搜索 POI
    │
    ▼
第 2 轮:AI 调用 amap_search_detail → 获取酒店图片 URL
    │
    ▼
第 3 轮:AI 调用 chrome_navigate → 打开第一个酒店图片
    │
    ▼
第 4 轮:AI 调用 chrome_navigate → 打开第二个酒店图片
    │
    ▼
第 5 轮:AI 调用 chrome_navigate → 打开第三个酒店图片
    │
    ▼
第 6 轮:AI 调用 chrome_set_title → 修改页面标题
    │
    ▼
第 7 轮:AI 直接回答 → 汇报完成情况

四、内容审查:代码中的 4 个问题与纠正

在审查学习资料中的代码时,我发现了以下几个问题,已在上方完整代码中修正:

问题 1:client.close() 变量名错误

原代码最后一行写的是 await client.close(),但实际创建的变量名是 mcpClient

javascript 复制代码
// ❌ 原代码(变量名不一致)
const mcpClient = new MultiServerMCPClient({ ... });
await client.close(); // client 未定义!

// ✅ 修正后
await mcpClient.close();

如果不修复,脚本运行完成后进程会一直挂着不退出,因为 MCP 子进程的 stdio 通道没有被正确关闭。

问题 2:工具返回值兼容性处理

原代码中 ToolMessage 直接使用 toolResult,但不同 MCP Server 的返回格式并不统一:

MCP Server 返回类型 示例
高德地图 string "北京南站附近有..."
File System { text: "..." } { text: "文件内容..." }
javascript 复制代码
// ❌ 原代码(未做类型兼容)
const toolResult = await foundTool.invoke(toolCall.args);
messages.push(new ToolMessage({
    content: toolResult,  // File System 返回对象时会丢失内容
    tool_call_id: toolCall.id
}));

// ✅ 修正后(兼容 string 和 { text } 对象)
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
}));

问题 3:API Key 硬编码在代码中

原代码将高德地图 API Key 直接写在 URL 里:

javascript 复制代码
// ❌ 原代码(Key 硬编码)
"url": "https://mcp.amap.com/mcp?key=07ddcb65c60cc1f788d2eb806edc846e"

// ✅ 建议改为环境变量
"url": `https://mcp.amap.com/mcp?key=${process.env.AMAP_KEY}`

.env 中添加 AMAP_KEY=你的Key,避免 API Key 泄露到 Git 仓库。

问题 4:缺少 File System Server 的安全说明

File System MCP Server 需要显式指定允许访问的目录路径。原代码使用了硬编码的绝对路径,不具备可移植性:

javascript 复制代码
// ❌ 原代码(硬编码绝对路径)
args: [
    '-y',
    '@modelcontextprotocol/server-filesystem',
    '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-mcp'
]

// ✅ 建议使用相对路径或环境变量
args: [
    '-y',
    '@modelcontextprotocol/server-filesystem',
    process.cwd()  // 当前工作目录
]

五、MCP 协议的两种通信方式深度对比

5.1 stdio vs HTTP

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                   MCP 通信方式对比                        │
├──────────┬──────────────────┬────────────────────────────┤
│   维度    │     stdio        │          HTTP              │
├──────────┼──────────────────┼────────────────────────────┤
│  通信     │ 父子进程 stdin/  │  HTTP 请求/响应             │
│  方式     │ stdout           │                            │
├──────────┼──────────────────┼────────────────────────────┤
│  适用     │ 本地工具          │  远程服务                   │
│  场景     │ (File System,    │  (高德地图,                │
│          │  Chrome DevTools)│   未来更多云端 MCP)        │
├──────────┼──────────────────┼────────────────────────────┤
│  启动     │ Agent 自动拉起    │  Server 已在运行            │
│  方式     │ 子进程            │  Agent 发送 HTTP 请求       │
├──────────┼──────────────────┼────────────────────────────┤
│  跨语言   │ ✅ 子进程是       │  ✅ HTTP 是语言无关的       │
│          │ 独立进程,任意语言  │  通用协议                   │
├──────────┼──────────────────┼────────────────────────────┤
│  典型     │ command + args    │  url (Streamable HTTP)    │
│  配置     │ (npx node 等)    │                            │
└──────────┴──────────────────┴────────────────────────────┘

5.2 Streamable HTTP

高德地图 MCP 使用的是 Streamable HTTP 传输方式,这是 MCP 协议较新的通信标准(之前还有 SSE 方式)。它的特点是:

  • 使用标准 HTTP POST 请求发送 MCP 消息
  • 支持流式响应,适合长时间运行的工具
  • 无需维持长连接,更适合云服务场景
  • 配置极为简洁,只需一个 url 字段
javascript 复制代码
// HTTP Server 只需 url,无需 command/args
'amap-maps-streamableHTTP': {
    "url": "https://mcp.amap.com/mcp?key=xxx"
}

// stdio Server 需要 command + args,Agent 会自动启动子进程
'filesystem': {
    command: 'npx',
    args: ['-y', '@modelcontextprotocol/server-filesystem', '/path']
}

六、AI 工作流的真正价值

6.1 为什么要用 MCP 而不是直接写代码?

传统方式下,要让 AI 查地图、控制浏览器、写文件,你需要:

  1. 手动集成高德地图 API SDK
  2. 使用 Puppeteer/Playwright 控制浏览器
  3. 用 Node.js fs 模块操作文件
  4. 维护大量胶水代码

使用 MCP 后:

  1. 配置即接入 --- 只需一个 JSON 配置对象
  2. 自动发现工具 --- getTools() 自动获取所有 Server 暴露的工具
  3. LLM 自主选择 --- AI 自己决定调用哪个工具、传什么参数
  4. 热插拔 --- 添加新 Server 只需加一个配置项,零代码修改

6.2 MCP 生态正在爆发

目前社区已有大量开箱即用的 MCP Server:

MCP Server 功能 接入方式
高德地图 位置/POI/路线 HTTP
Chrome DevTools 浏览器控制 stdio (npx)
File System 文件读写 stdio (npx)
GitHub Issue/PR/代码搜索 stdio (npx)
Slack 消息发送/频道管理 stdio (npx)
Database (MySQL/PostgreSQL) 数据库查询 stdio (npx)
Puppeteer 网页截图/爬取 stdio (npx)

任何人都可以开发 MCP Server,只要遵循 MCP 协议规范,就能被所有兼容的 Agent 直接使用。这就是 MCP 的核心价值:一次开发,处处复用


七、ReAct 循环中的工具执行策略

7.1 串行执行 vs 并行执行

当前代码采用串行执行 策略:逐个执行 tool_calls

javascript 复制代码
// 串行:一个完成后再执行下一个
for (const toolCall of response.tool_calls) {
    const toolResult = await foundTool.invoke(toolCall.args);
    // ...
}

并行执行的风险:

javascript 复制代码
// 并行:所有工具同时发起
await Promise.all(
    response.tool_calls.map(tc => foundTool.invoke(tc.args))
);
执行方式 优点 风险
串行 (for...of) 结果可控、便于调试 速度较慢
并行 (Promise.all) 速度快 任一失败则全部失败;已发起的异步任务不受控制继续执行

当多个工具调用之间没有依赖关系 时(如同时查 3 个酒店信息),并行执行效率更高。但需要注意 Promise.all 的"快速失败"特性:只要有一个 Promise reject,整个 Promise.all 就会 reject,而其他已经发起的异步请求不会被取消

7.2 ToolMessage 必须携带 tool_call_id

javascript 复制代码
messages.push(new ToolMessage({
    content: contentStr,
    tool_call_id: toolCall.id  // ← 关键!必须携带
}));

tool_call_id 是 LLM 关联"工具调用请求"和"工具执行结果"的唯一标识。如果不带这个字段,LLM 无法知道哪个结果对应哪个调用,会导致后续推理出错。


八、知识图谱

arduino 复制代码
远程 MCP 实战知识体系
│
├── MCP 通信方式
│   ├── stdio(本地子进程)
│   │   ├── command + args 配置
│   │   ├── Agent 自动拉起子进程
│   │   └── 适用:File System, Chrome DevTools, GitHub
│   │
│   └── HTTP(远程服务)
│       ├── url 配置(Streamable HTTP)
│       ├── Server 已在运行,Agent 发请求
│       └── 适用:高德地图, 未来更多云端服务
│
├── MultiServerMCPClient
│   ├── mcpServers 配置对象
│   ├── getTools() 自动发现工具
│   └── close() 关闭所有连接和子进程
│
├── MCP Server 生态
│   ├── 官方 Server(高德地图)
│   ├── 社区 Server(File System, Chrome DevTools)
│   └── 自建 Server(自定义业务工具)
│
├── ReAct 循环
│   ├── HumanMessage → LLM 推理
│   ├── tool_calls → 工具执行
│   ├── ToolMessage → 结果回传
│   └── 无 tool_calls → 最终回答
│
└── 注意事项
    ├── API Key 使用环境变量
    ├── File System 指定允许目录(安全)
    ├── 工具返回值类型兼容处理
    └── mcpClient.close() 释放资源

总结

本文从本地 MCP Server 进阶到远程 MCP 生态,核心收获:

  1. MCP 的两种通信方式stdio 适合本地工具,HTTP 适合远程服务,MultiServerMCPClient 统一管理
  2. 配置即接入:只需一个 JSON 配置对象,就能接入任何 MCP Server,零胶水代码
  3. 多 Server 协作:高德地图查位置 + Chrome DevTools 控制浏览器 + File System 写文件,AI 自主编排工作流
  4. 注意事项 :API Key 环境变量化、工具返回值类型兼容、close() 释放资源、tool_call_id 必须携带

MCP 的终极愿景是:AI Agent 的工具生态,像手机 App Store 一样繁荣。任何开发者都能发布 MCP Server,任何 Agent 都能即插即用。


参考资料


本文基于课堂实战学习内容整理,经内容审查与代码纠错后输出。如果觉得有帮助,欢迎点赞收藏!

相关推荐
浮生望7 小时前
MCP 协议:打破工具围墙,让 AI Agent 跨语言跨进程调用任意工具
agent·mcp
小林ixn8 小时前
远程 MCP 实战:让 AI 无缝调用地图、浏览器与文件系统
人工智能·agent·mcp
小林ixn9 小时前
AI Agent的“万能工具箱”:MCP协议实战,让工具调用不再受语言和进程束缚
人工智能·agent·mcp
冬奇Lab9 小时前
MCP 系列(03):生态导航——官方 Server 清单与社区精选
人工智能·资讯·mcp
Esaka_Forever10 小时前
MCP(Model‑Context‑Protocol)整体架构
mcp
To_OC11 小时前
MCP 入门实战:写一个能读本地文件的极简服务
人工智能·agent·mcp
月走乂山20 小时前
Anything Analyzer MCP 401 Unauthorized 故障排查
electron·故障排查·mcp·claude code·anything analyzer
Darling噜啦啦1 天前
LangChain 集成 MCP 全链路实战:MultiServerMCPClient 自动获取工具与 Resource 资源注入
mcp
ServBay1 天前
你还在手写 Prompt?聪明的人早就用上了循环工程,AI 的自动驾驶时代来了
ai编程·claude·mcp