中文资讯 掘金 AI Sat, 11 Jul 2026 正文 ok 1502 words

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

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

本文演示如何通过 MultiServerMCPClient 同时接入高德地图(HTTP 远程)、Chrome DevTools(stdio)和 File System(stdio)三个 MCP Server,让 AI Agent 自主完成“查酒店、开浏览器、改标题、写文件”的复杂工作流。全文提供可运行的完整代码,并纠正了原始资料中的四个常见问题:变量名错误、返回值兼容性处理、API Key 硬编码与文件系统路径安全隐患。同时深入对比了 stdio 与 HTTP 两种通信方式,分析了 ReAct 循环中串行/并行工具执行策略及其风险。对开发者而言,本文展现了 MCP “配置即接入、工具自动发现、一次开发处处复用”的生态价值,为构建更强大的 AI Agent 提供了可直接复用的实践范本。
打开原文相关 6 · 机会 2
media3.2 KB media589 B media4.8 KB

中文

远程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 仓库或全局安装:

'filesystem': { command: 'npx', args: [ '-y', '@modelcontextprotocol/server-filesystem',

    '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-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';

const model = new ChatOpenAI({ modelName: 'deepseek-v4-pro', apiKey: process.env.DEEPSEEK_API_KEY, temperature: 0, configuration: { baseURL: 'https://api.deepseek.com/v1', }, });

const mcpClient = new MultiServerMCPClient({ mcpServers: {

    'amap-maps-streamableHTTP': {
        "url": "https://mcp.amap.com/mcp?key=你的高德Key"
    },
    
    'filesystem': {
        command: 'npx',
        args: [
            '-y',
            '@modelcontextprotocol/server-filesystem',
            '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-mcp'
        ]
    },
    
    
    'chrome-devtools': {
        command: 'npx',
        args: [
            '-y',
            'chrome-devtools-mcp@latest',
        ]
    }
}

});

const tools = await mcpClient.getTools();

console.log(已加载 ${tools.length} 个工具:); tools.forEach(t => console.log( - ${t.name}));

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(`第${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;
            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( "北京南站附近的酒店,最近的 3 个酒店," + "拿到酒店图片,打开浏览器,展示每个酒店的图片," + "每个 tab 一个 url 展示,并且把那个页面标题改为酒店名" );

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();

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,
tool_call_id: toolCall.id }));

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 里:

"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 字段

'amap-maps-streamableHTTP': { "url": "https://mcp.amap.com/mcp?key=xxx" }

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

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 生态,核心收获:

  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 都能即插即用。


参考资料


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

English / 原文

远程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 仓库或全局安装:

'filesystem': {
    command: 'npx',
    args: [
        '-y',
        '@modelcontextprotocol/server-filesystem',
        
        '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-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';


const model = new ChatOpenAI({
    modelName: 'deepseek-v4-pro',
    apiKey: process.env.DEEPSEEK_API_KEY,
    temperature: 0,
    configuration: {
        baseURL: 'https://api.deepseek.com/v1',
    },
});


const mcpClient = new MultiServerMCPClient({
    mcpServers: {
        
        'amap-maps-streamableHTTP': {
            "url": "https://mcp.amap.com/mcp?key=你的高德Key"
        },
        
        'filesystem': {
            command: 'npx',
            args: [
                '-y',
                '@modelcontextprotocol/server-filesystem',
                '/Users/darling/Desktop/workspace/hzs_ai/ai/agent_in_action/remote-mcp'
            ]
        },
        
        
        'chrome-devtools': {
            command: 'npx',
            args: [
                '-y',
                'chrome-devtools-mcp@latest',
            ]
        }
    }
});


const tools = await mcpClient.getTools();

console.log(`已加载 ${tools.length} 个工具:`);
tools.forEach(t => console.log(`  - ${t.name}`));

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(`第${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;
                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(
    "北京南站附近的酒店,最近的 3 个酒店," +
    "拿到酒店图片,打开浏览器,展示每个酒店的图片," +
    "每个 tab 一个 url 展示,并且把那个页面标题改为酒店名"
);


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(); 


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,  
    tool_call_id: toolCall.id
}));


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 里:

"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 字段

    'amap-maps-streamableHTTP': { "url": "https://mcp.amap.com/mcp?key=xxx" }

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

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 生态,核心收获:

  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 都能即插即用。


参考资料


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