Skip to content

高德 MCP + 浏览器 MCP:LangChain 复用别人的 MCP Server 有多爽

上节我们学了 MCP:自己实现了一个 MCP Server,然后在 Cursor 或者 LangChain 里连上这个 Server,就可以用里面的 tools 了。

它本质上还是 Tool,只不过包了一层进程,可以通过 stdio 和 http 来访问。

有了这一层协议之后,有个巨大的好处:任何人都可以开发基于这个协议的 MCP Server,然后我们可以直接复用! 比如上节我们写的那个 MCP Server 就可以被别人用。

这节我们用一下别人写好的 MCP Server,感受下 MCP 有多爽!用这三个:

  • 高德 MCP:做位置查询、路线规划等
  • Chrome DevTools MCP:控制浏览器,打开关闭页面、点击元素、截图等
  • FileSystem MCP:读写文件、创建目录等

高德 MCP:先申请 API Key

首先是高德 MCP,我们需要先获取一个 API Key:

  • 地址:https://developer.amap.com/
  • 创建应用,然后创建一个 API Key,类型选 Web 服务 就行

然后先在 Cursor 里测试下这个 MCP 服务是否可用。配好之后,就可以查到这个 MCP Server 里的一堆 tool 了。

记得我们说过 MCP 有两种接入方式么?这就是 http 的接入方式

json
"amap-maps-streamableHTTP": {
  "url": "https://mcp.amap.com/mcp?key=你的 api key"
}

当然,高德也支持 stdio 的本地进程接入方式,这样写:

json
"amap-maps": {
  "command": "npx",
  "args": [
    "-y",
    "@amap/amap-maps-mcp-server"
  ],
  "env": {
    "AMAP_MAPS_API_KEY": "你的 api key"
  }
}

就是用 npx 跑一个 npm 包,会创建一个支持 stdio 连接的进程,然后连上其中的 MCP Server 就好了。这个 MCP Server 里肯定封装了和高德服务端的通信,本质上是一样的。

顺带说一句:这个技能完全可以写进简历——"我开发了一个 MCP Server 的 npm 包,包含 xxx tool,支持 stdio 访问,可以在 Cursor 或 LangChain 里用 npx 执行来连上这个 MCP Server"。面试官一看就知道,这个人是真懂 MCP 的,而且还有实践经验。

在 LangChain 里用高德 MCP

说回正题,在 tool-test 项目里创建 src/mcp-test.mjs

js
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: "qwen-plus",
  apiKey: process.env.OPENAI_API_KEY,
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL,
  },
});

const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    'my-mcp-server': {
      command: "node",
      args: [
        "/Users/guang/code/tool-test/src/my-mcp-server.mjs"
      ]
    },
    "amap-maps-streamableHTTP": {
      "url": "https://mcp.amap.com/mcp?key=" + process.env.AMAP_MAPS_API_KEY
    },
  }
});

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);
        messages.push(new ToolMessage({
          content: toolResult,
          tool_call_id: toolCall.id,
        }));
      }
    }
  }

  return messages[messages.length - 1].content;
}

await runAgentWithTools("北京南站附近的酒店,以及去的路线");
await mcpClient.close();

MCP Client 的代码和上节一样,用 @langchain/mcp-adapters:拿到其中的 tools 绑定给 model,然后调用 model,如果有 tool_calls 就调用下,把工具调用结果封装为 ToolMessage 传给大模型继续处理。这里的高德 API Key 同样放到了 .env 里。

先注释掉高德 MCP Server 跑一下:可以看到,大模型没法处理地理位置信息,让你用地图。

启用高德 MCP Server 后再跑:大模型就可以调用高德 MCP 里的 tool,给出酒店位置和路线了!

这就是 MCP 的好处——直接复用别人写好的 Tool。

FileSystem MCP:文件读写也不用自己写了

然后文件读写、创建目录这种,也不用自己写 Tool,可以用现成的 MCP——MCP 官方维护的一个 MCP Server:

json
"filesystem": {
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    ...(process.env.ALLOWED_PATHS.split(',') || '')
  ]
}

后面是可访问的目录,我们配在 .env 里,逗号分隔。

可以先在 Cursor 里配置下这个 MCP 服务,看下有哪些 tool:有文件读写、目录创建、文件移动等 tool。配上这个 MCP,大模型就有文件读写能力了。

不过这里有个坑注意下:一般我们写 Tool 都是直接返回字符串,但是 FileSystem MCP 封装的这些 Tool 返回的是对象,有 text 属性,所以要处理下:

js
// 确保 content 是字符串类型
let contentStr;
if (typeof toolResult === 'string') {
  contentStr = toolResult;
} else if (toolResult && toolResult.text) {
  // 如果返回对象有 text 字段,优先使用
  contentStr = toolResult.text;
}

messages.push(new ToolMessage({
  content: contentStr,
  tool_call_id: toolCall.id,
}));

改下提示词跑跑试试:

js
await runAgentWithTools("北京南站附近的 5 个酒店,以及去的路线,路线规划生成文档保存到 /Users/guang/Documents/...");

可以看到,大模型首先调用高德 MCP 拿到了附近的酒店位置,然后规划了路线,最后调用 FileSystem MCP 写入了文件。

直接复用别人的 MCP,完全不用自己写。你自己写的 Tool 想给别人用,也可以封装成 MCP,最好发个 npm 包,这样还可以写到简历上,让面试官用。

Chrome DevTools MCP:浏览器自动化

最后用一下 Chrome DevTools 的 MCP,它可以用来做浏览器自动化,比如打开页面、点击元素、截图等。

在 Cursor 里配置:

json
"chrome-devtools": {
  "command": "npx",
  "args": [
    "-y",
    "chrome-devtools-mcp@latest"
  ]
}

可以看到很多工具。在 LangChain 里调用下,改下提示词:

js
await runAgentWithTools("北京南站附近的酒店,最近的 3 个酒店,拿到酒店图片,打开浏览器,展示每个酒店的图片");

运行后可以看到:搜到了北京南站最近的 3 个酒店,并且浏览器打开了酒店图片。

只要配好 MCP,大模型就可以直接调用里面的 tools 了。

常见问题

模型没有调用工具,而是"描述它会做什么"就结束了? 因为 messages 里只有一条 HumanMessage,没有 SystemMessage 引导模型主动调用工具。有些模型(特别是 qwen 系列)需要明确的系统提示才会主动执行工具调用。加一条 SystemMessage 即可,比如:"你是一个智能助手,拥有多种工具能力。当用户提出需求时,你必须直接调用工具来完成任务,而不是描述你会怎么做。"

高德接口报 QPS_HAS_EXCEEDED_THE_LIMIT 高德个人版 API 有 QPS 限制(3 次/秒)。可以在系统提示词里设计分批调用规则(每批最多 3 个,等上一批返回后再发起下一批),或者在工具调用时添加延迟。

浏览器无法直接控制? 把其他浏览器关掉再运行。这与 userDataDir 有关,一个用户目录只能跑一个浏览器实例。

打开浏览器后想让它不自动关闭? 注释掉 await mcpClient.close(),不关闭 MCP 进程浏览器就不会自动关掉;或者改配置加 --browserUrl 参数指向本地已存在的 Chrome。

去哪找现成的 MCP? 去专门做相关业务的平台,比如天气预报找"墨迹天气",股票数据找"同花顺",一般都会提供官方 MCP Server。

FileSystem MCP 报 Invalid input: expected "object" 这是 @langchain/mcp-adapters 加载 MCP 工具时校验 inputSchema.type 必须是字符串 "object",而 server-filesystem 返回的 schema 里 type 缺失或为数组,两者兼容性问题。等修复,或换其他实现规范的 filesystem MCP 服务。

MCP 太多会不会费 token? 会,执行时会把所有 MCP 的 tools 都添加到内存中。可以显式指定使用哪个 MCP(在 prompt 里点名),比全量加载更省 token。

Windows 下路径注意ALLOWED_PATHS 后面用从根目录开始的绝对路径(如 C:\...),给大模型的提示词里也是。

总结

这节我们使用了高德、FileSystem、Chrome DevTools 的 MCP,用它们组合来实现了一些功能。这些 MCP Server 有的是 stdio 本地进程调用,有的是 http 远程进程调用。

MCP 的一大好处就是:别人开发好的,可以直接用

你全程不需要知道怎么用高德的 API 查询位置、路线,不需要知道怎么用 CDP 协议控制浏览器。你只需要把这些 MCP 给到 AI,让它自己去调用——大模型会自己读取 tool 描述来传入参数调用。

是不是特别爽!

基于 VitePress 构建 · 专注前端与 AI 实战