远程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 的生态优势
传统工具调用方式:
Agent → 自己写代码集成 Tool → 耦合、难复用
MCP 标准化方式:
Agent → MCP 协议 → 任何人开发的 MCP Server → 即插即用
MCP 本质上就是 Tool + 进程封装 + 标准化通信。通过 stdio 或 HTTP,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 直接连接,无需安装任何依赖:
'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 仓库或全局安装:
// 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:
chrome --remote-debugging-port=9222
通信方式对比图
┌─────────────────────────────────────────────────────┐
│ 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 项目初始化
mkdir remote-mcp && cd remote-mcp
npm init -y
npm install @langchain/mcp-adapters @langchain/openai @langchain/core chalk dotenv
创建 .env 文件:
DEEPSEEK_API_KEY=sk-your-deepseek-key
3.2 核心 Agent 代码(mcp-test.mjs)
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 代码执行流程
用户提问:"北京南站附近的酒店..."
│
▼
第 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。
// ❌ 原代码(变量名不一致)
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: "文件内容..." } |
// ❌ 原代码(未做类型兼容)
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 里:
// ❌ 原代码(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 需要显式指定允许访问的目录路径。原代码使用了硬编码的绝对路径,不具备可移植性:
// ❌ 原代码(硬编码绝对路径)
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
┌─────────────────────────────────────────────────────────┐
│ 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字段
// 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 查地图、控制浏览器、写文件,你需要:
- 手动集成高德地图 API SDK
- 使用 Puppeteer/Playwright 控制浏览器
- 用 Node.js fs 模块操作文件
- 维护大量胶水代码
使用 MCP 后:
- 配置即接入 — 只需一个 JSON 配置对象
- 自动发现工具 —
getTools()自动获取所有 Server 暴露的工具 - LLM 自主选择 — AI 自己决定调用哪个工具、传什么参数
- 热插拔 — 添加新 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。
// 串行:一个完成后再执行下一个
for (const toolCall of response.tool_calls) {
const toolResult = await foundTool.invoke(toolCall.args);
// ...
}
并行执行的风险:
// 并行:所有工具同时发起
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
messages.push(new ToolMessage({
content: contentStr,
tool_call_id: toolCall.id // ← 关键!必须携带
}));
tool_call_id 是 LLM 关联"工具调用请求"和"工具执行结果"的唯一标识。如果不带这个字段,LLM 无法知道哪个结果对应哪个调用,会导致后续推理出错。
八、知识图谱
远程 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 生态,核心收获:
- MCP 的两种通信方式:
stdio适合本地工具,HTTP适合远程服务,MultiServerMCPClient统一管理 - 配置即接入:只需一个 JSON 配置对象,就能接入任何 MCP Server,零胶水代码
- 多 Server 协作:高德地图查位置 + Chrome DevTools 控制浏览器 + File System 写文件,AI 自主编排工作流
- 注意事项:API Key 环境变量化、工具返回值类型兼容、
close()释放资源、tool_call_id必须携带
MCP 的终极愿景是:AI Agent 的工具生态,像手机 App Store 一样繁荣。任何开发者都能发布 MCP Server,任何 Agent 都能即插即用。
参考资料
- MCP 官方协议规范
- 高德地图 MCP Server
- @modelcontextprotocol/server-filesystem
- chrome-devtools-mcp
- @langchain/mcp-adapters
本文基于课堂实战学习内容整理,经内容审查与代码纠错后输出。如果觉得有帮助,欢迎点赞收藏!