前言
2026 年最火的 AI 协议是什么?
MCP(Model Context Protocol)------模型上下文协议。
几乎所有 AI 产品都在接入:Cursor、Claude、豆包、Trae...
今天我们从原理出发,实战接入高德地图 MCP 、文件系统 MCP 、浏览器控制 MCP,彻底搞懂 MCP 的本质。
一、MCP 是什么?
1.1 从"聊天"到"干活"
diff
豆包手机 AI Agent 的全新体验:
- 不再只是 chat
- 帮我们把活干了
传统 AI 的局限:
| 能力 | 传统 AI | MCP 加持后 |
|---|---|---|
| 聊天 | ✅ 可以 | ✅ 可以 |
| 查地图 | ❌ 不能 | ✅ 调用高德地图 |
| 读写文件 | ❌ 不能 | ✅ 操作本地文件 |
| 控制浏览器 | ❌ 不能 | ✅ 打开 Chrome |
| 查数据库 | ❌ 不能 | ✅ 查询 MySQL |
MCP 的本质:
ini
MCP = AI 的"USB 接口"
就像 USB 让各种设备可以连接电脑一样,MCP 让 AI 可以连接各种外部服务。
1.2 MCP 的定义
Model Context Protocol(模型上下文协议)
25 年最火的协议,定义了模型和外界服务之间的通信标准。
核心思想:
- 标准化:统一的协议格式,一次开发,到处使用
- 可扩展:任何人都可以开发 MCP Server
- 安全:权限控制,用户授权
MCP 的架构:
arduino
┌─────────────┐
│ AI 模型 │ ← 大脑(LLM)
└──────┬──────┘
│
▼
┌─────────────┐
│ MCP Client │ ← 客户端(Cursor/Trae/你的代码)
└──────┬──────┘
│
├──────────────┬──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 高德地图 │ │ 文件系统 │ │ 浏览器 │
│ MCP │ │ MCP │ │ MCP │
└──────────┘ └──────────┘ └──────────┘
三个角色:
| 角色 | 职责 | 例子 |
|---|---|---|
| AI 模型 | 思考和决策 | GPT-4、Claude、Qwen |
| MCP Client | 协议适配、工具调用 | Cursor、你的代码 |
| MCP Server | 提供具体能力 | 高德地图、文件系统 |
二、MCP 的两种接入方式
2.1 Stdio 方式(本地进程)
js
{
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
}
特点:
- 启动一个本地进程
- 通过标准输入/输出(stdin/stdout)通信
- 适合本地工具(文件系统、数据库等)
2.2 HTTP 方式(远程服务)
js
{
url: "https://mcp.amap.com/mcp?key=YOUR_API_KEY"
}
特点:
- 连接远程服务
- 通过 HTTP 请求通信
- 适合云服务(地图、天气、API 等)
对比:
| 方式 | 通信方式 | 适用场景 | 性能 |
|---|---|---|---|
| Stdio | 本地进程 | 本地工具 | 快 |
| HTTP | 网络请求 | 云服务 | 受网络影响 |
三、实战:接入三个 MCP Server
3.1 项目结构
bash
mcp-test/
├── main.mjs # 主程序
├── package.json
├── .env # 环境变量(API Key)
└── beijing_south_station_hotels.md # AI 生成的文档
3.2 完整代码
js
// main.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. 初始化 AI 模型
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
// 2. 配置多个 MCP Server
const mcpClient = new MultiServerMCPClient({
mcpServers: {
// 高德地图 MCP(HTTP 方式)
"amap-maps-streamableHTTP": {
url: `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`,
},
// 文件系统 MCP(Stdio 方式)
filesystem: {
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\workspace\\lesson_zp\\ai\\agent\\mcp_in_action\\mcp-test",
],
},
// 浏览器控制 MCP(Stdio 方式)
"chrome-devtools": {
command: "npx",
args: ["-y", "chrome-devtools-mcp@latest"],
},
},
});
// 3. 获取所有工具
const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);
// 4. Agent 循环
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);
// 5. 如果没有工具调用,返回最终结果
if (!response.tool_calls || response.tool_calls.length === 0) {
console.log(`\n AI 最终回复:\n ${response.content}\n`);
return response.content;
}
// 6. 处理工具调用
console.log(
chalk.bgBlue(`🔍 检测到 ${response.tool_calls.length} 个工具调用`)
);
console.log(
chalk.bgBlue(
`🔍 工具调用: ${response.tool_calls.map((t) => t.name).join(", ")}`
)
);
// 7. 执行每个工具
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;
}
// 8. 执行任务
await runAgentWithTools(`
北京南站附近的3个酒店,拿到酒店照片,展开浏览器,展示每个酒店的图片
每个tab一个url展示,并且把那个页面标题改为酒店名
`);
await mcpClient.close();
四、逐行解析
4.1 初始化 AI 模型
js
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
逐行解析:
js
modelName: process.env.MODEL_NAME,
- 从环境变量读取模型名称
- 可以是
gpt-4、qwen-max、deepseek-chat等
js
apiKey: process.env.OPENAI_API_KEY,
- API 密钥,用于身份验证
- 不要硬编码在代码中,使用
.env文件
js
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
}
- API 基础 URL
- 如果使用国内模型(如 Qwen),需要修改为对应的 baseURL
为什么用环境变量?
- 安全性:API Key 不会泄露到代码仓库
- 灵活性:不同环境使用不同配置
- 标准化:12-Factor App 最佳实践
4.2 配置 MCP Server
js
const mcpClient = new MultiServerMCPClient({
mcpServers: {
"amap-maps-streamableHTTP": {
url: `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`,
},
filesystem: {
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\workspace\\lesson_zp\\ai\\agent\\mcp_in_action\\mcp-test",
],
},
"chrome-devtools": {
command: "npx",
args: ["-y", "chrome-devtools-mcp@latest"],
},
},
});
逐行解析:
js
"amap-maps-streamableHTTP": {
url: `https://mcp.amap.com/mcp?key=${process.env.AMAP_MAPS_API_KEY}`,
}
- 高德地图 MCP,使用 HTTP 方式
url:MCP Server 的地址key:高德地图 API Key,需要在高德开放平台申请
js
filesystem: {
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\workspace\\lesson_zp\\ai\\agent\\mcp_in_action\\mcp-test",
],
}
- 文件系统 MCP,使用 Stdio 方式
command: "npx":使用 npx 运行args:-y:自动确认安装@modelcontextprotocol/server-filesystem:官方文件系统 MCP 包- 最后一个参数:允许访问的目录路径
js
"chrome-devtools": {
command: "npx",
args: ["-y", "chrome-devtools-mcp@latest"],
}
- 浏览器控制 MCP
chrome-devtools-mcp@latest:使用最新版本- 可以控制 Chrome 浏览器(打开页面、点击、输入等)
三个 MCP Server 对比:
| Server | 方式 | 功能 | 用途 |
|---|---|---|---|
| 高德地图 | HTTP | 查地点、路线、POI | 地理信息查询 |
| 文件系统 | Stdio | 读写文件 | 生成文档、保存结果 |
| 浏览器控制 | Stdio | 控制 Chrome | 展示内容、自动化操作 |
4.3 获取工具并绑定
js
const tools = await mcpClient.getTools();
const modelWithTools = model.bindTools(tools);
逐行解析:
js
const tools = await mcpClient.getTools();
- 从所有 MCP Server 获取工具列表
- 每个工具包含:名称、描述、参数 schema
- 例如:
maps_geo(地理编码)、maps_direction(路线规划)等
js
const modelWithTools = model.bindTools(tools);
- 将工具绑定到 AI 模型
- AI 可以根据用户请求,自动选择调用哪个工具
- 这就是 Function Calling(函数调用)机制
Function Calling 流程:
arduino
用户:北京南站附近的酒店
↓
AI 思考:需要调用地图工具
↓
AI 输出:调用 maps_search_around 工具
↓
MCP Client:执行工具,返回结果
↓
AI 思考:根据结果生成回复
↓
最终回复:推荐 3 个酒店...
4.4 Agent 循环
js
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);
逐行解析:
js
const messages = [new HumanMessage(query)];
- 创建消息数组,初始包含用户的问题
HumanMessage:用户消息- 后续会不断追加 AI 回复和工具结果
js
for (let i = 0; i < maxIterations; i++) {
- Agent 循环,最多执行 30 次
- 防止无限循环(AI 一直调用工具)
js
const response = await modelWithTools.invoke(messages);
messages.push(response);
- 调用 AI 模型,传入所有历史消息
- AI 会返回:
- 纯文本回复(结束)
- 工具调用(继续循环)
4.5 判断是否结束
js
if (!response.tool_calls || response.tool_calls.length === 0) {
console.log(`\n AI 最终回复:\n ${response.content}\n`);
return response.content;
}
逐行解析:
js
if (!response.tool_calls || response.tool_calls.length === 0) {
- 检查 AI 是否调用了工具
- 如果没有工具调用,说明 AI 已经给出最终答案
js
console.log(`\n AI 最终回复:\n ${response.content}\n`);
return response.content;
- 输出最终回复
- 返回结果,结束函数
4.6 处理工具调用
js
console.log(
chalk.bgBlue(`🔍 检测到 ${response.tool_calls.length} 个工具调用`)
);
console.log(
chalk.bgBlue(
`🔍 工具调用: ${response.tool_calls.map((t) => t.name).join(", ")}`
)
);
解析:
- 输出检测到的工具调用数量
- 输出工具名称列表
- 使用
chalk库给日志上色,便于调试
4.7 执行工具
js
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,
})
);
}
}
逐行解析:
js
for (const toolCall of response.tool_calls) {
- 遍历所有工具调用
- AI 可能一次调用多个工具
js
const foundTool = tools.find((t) => t.name === toolCall.name);
- 根据工具名称找到对应的工具实例
js
const toolResult = await foundTool.invoke(toolCall.args);
- 执行工具,传入参数
- 返回工具执行结果
js
let contentStr;
if (typeof toolResult === "string") {
contentStr = toolResult;
} else if (toolResult && toolResult.text) {
contentStr = toolResult.text;
}
- 处理工具返回结果
- 可能是字符串,也可能是对象(包含
text属性)
js
messages.push(
new ToolMessage({
content: contentStr,
tool_call_id: toolCall.id,
})
);
- 创建
ToolMessage,包含工具结果 tool_call_id:关联到对应的工具调用- 追加到消息历史,供 AI 继续思考
消息类型:
| 类型 | 用途 | 例子 |
|---|---|---|
HumanMessage |
用户输入 | "北京南站附近的酒店" |
AIMessage |
AI 回复 | "我来帮你查询..." |
ToolMessage |
工具结果 | "找到 3 个酒店..." |
SystemMessage |
系统提示 | "你是一个助手..." |
4.8 执行任务
js
await runAgentWithTools(`
北京南站附近的3个酒店,拿到酒店照片,展开浏览器,展示每个酒店的图片
每个tab一个url展示,并且把那个页面标题改为酒店名
`);
await mcpClient.close();
逐行解析:
js
await runAgentWithTools(`...`);
- 执行 Agent,传入复杂任务
- 任务包含多个步骤:
- 查询北京南站附近的酒店
- 获取酒店照片
- 打开浏览器展示
- 修改页面标题
js
await mcpClient.close();
- 关闭 MCP Client
- 释放资源,断开连接
五、执行流程
5.1 完整流程图
erlang
用户输入:北京南站附近的3个酒店...
↓
AI 思考:需要调用地图工具
↓
调用 maps_search_around(高德地图 MCP)
↓
返回:3 个酒店信息
↓
AI 思考:需要获取酒店照片
↓
调用 maps_photo(高德地图 MCP)
↓
返回:照片 URL
↓
AI 思考:需要打开浏览器展示
↓
调用 chrome_navigate(浏览器 MCP)
↓
打开 3 个 Tab,展示照片
↓
调用 chrome_set_document_title(浏览器 MCP)
↓
修改页面标题
↓
AI 思考:任务完成
↓
最终回复:已完成,展示了 3 个酒店...
5.2 实际输出示例
erlang
⏳正在等待AI思考...
🔍 检测到 1 个工具调用
🔍 工具调用: maps_search_around
⏳正在等待AI思考...
🔍 检测到 3 个工具调用
🔍 工具调用: maps_photo, maps_photo, maps_photo
⏳正在等待AI思考...
🔍 检测到 3 个工具调用
🔍 工具调用: chrome_navigate, chrome_navigate, chrome_navigate
⏳正在等待AI思考...
🔍 检测到 3 个工具调用
🔍 工具调用: chrome_set_document_title, chrome_set_document_title, chrome_set_document_title
⏳正在等待AI思考...
AI 最终回复:
已完成,展示了 3 个酒店的图片:
1. 米家青年酒店(北京南站店)
2. 汉庭酒店(北京南站护城河店)
3. 如家酒店(北京南站店)
六、生成的文档
AI 还会自动生成一个 Markdown 文档:
markdown
# 北京南站附近酒店及交通指南
## 北京南站位置信息
- **名称**: 北京南站
- **地址**: 北京市丰台区
- **坐标**: 116.378059, 39.867679
## 推荐酒店
### 1. 米家青年酒店(北京南站店)
- **地址**: 右外东庄21号楼
- **坐标**: 116.377765, 39.868995
- **评分**: 3.6分
- **距离北京南站**: 约304米步行
- **步行时间**: 约4分钟
### 2. 汉庭酒店(北京南站护城河店)
- **地址**: 开阳路6号院1号楼
- **评分**: 4.6分
- **距离北京南站**: 约851米步行
- **步行时间**: 约11分钟
这个文档是怎么生成的?
- AI 调用高德地图 MCP,获取酒店信息
- AI 调用文件系统 MCP,创建 Markdown 文件
- AI 将信息整理成结构化文档
七、扩展:MCP 的未来
7.1 MCP 生态
diff
MCP Server 生态(部分):
官方提供:
- filesystem(文件系统)
- postgres(数据库)
- sqlite(数据库)
- puppeteer(浏览器自动化)
第三方提供:
- 高德地图(地理信息)
- 百度搜索(搜索引擎)
- GitHub(代码仓库)
- Slack(即时通讯)
- Notion(笔记)
- ...
7.2 MCP vs 传统 API
| 特性 | 传统 API | MCP |
|---|---|---|
| 标准化 | 每个 API 不同 | 统一协议 |
| 开发成本 | 需要适配每个 API | 一次开发,到处使用 |
| AI 集成 | 需要手动编写调用逻辑 | AI 自动选择工具 |
| 生态 | 分散 | 统一生态 |
7.3 MCP 的应用场景
markdown
1. AI 编程助手(Cursor/Trae)
- 读写文件
- 执行命令
- 搜索代码
2. AI 办公助手
- 查地图、订酒店
- 发邮件、发消息
- 操作 Excel、PPT
3. AI 数据分析
- 查询数据库
- 生成报表
- 可视化图表
4. AI 自动化
- 控制浏览器
- 操作手机 App
- 定时任务
八、总结
核心要点
- MCP = AI 的"USB 接口",让 AI 可以连接各种外部服务
- 两种接入方式:Stdio(本地)和 HTTP(远程)
- 三个角色:AI 模型、MCP Client、MCP Server
- Function Calling:AI 自动选择并调用工具
- Agent 循环:思考 → 调用工具 → 获取结果 → 继续思考
实战收获
通过这个项目,我们实现了:
- ✅ 接入高德地图 MCP(HTTP 方式)
- ✅ 接入文件系统 MCP(Stdio 方式)
- ✅ 接入浏览器控制 MCP(Stdio 方式)
- ✅ 实现 Agent 循环,自动调用多个工具
- ✅ 完成复杂任务:查酒店 → 获取照片 → 打开浏览器 → 修改标题
MCP 的价值
arduino
传统 AI:只能聊天
MCP 加持:可以干活
从"Chat AI"到"Agent AI"的跨越