
适合读者:完全新手、前端开发者、对大模型工具调用感兴趣的工程师
技术栈示例:Vue + Cesium + Node.js + WebSocket + MCP
教程目标:看懂并搭建一套"用户通过聊天输入指令,大模型决定调用工具,再驱动地图执行动作"的完整链路
目录
- [1. 这篇教程要解决什么问题](#1. 这篇教程要解决什么问题)
- [2. 先别写代码:先搞懂两个很像但本质不同的方案](#2. 先别写代码:先搞懂两个很像但本质不同的方案)
- [2.1 方案一:前端直连模型](#2.1 方案一:前端直连模型)
- [2.2 方案二:真正完整的 MCP](#2.2 方案二:真正完整的 MCP)
- [2.3 它们最核心的区别](#2.3 它们最核心的区别)
- [3. 为什么很多人一开始会把两套方案混在一起](#3. 为什么很多人一开始会把两套方案混在一起)
- [4. 先建立整体认知:完整 MCP 里有哪些角色](#4. 先建立整体认知:完整 MCP 里有哪些角色)
- [5. 完整 MCP 的时序图:一句"飞到上海"是怎么穿过整个系统的](#5. 完整 MCP 的时序图:一句“飞到上海”是怎么穿过整个系统的)
- [6. 为什么这个架构更适合地图场景](#6. 为什么这个架构更适合地图场景)
- [7. 本教程的 demo 架构](#7. 本教程的 demo 架构)
- [8. 实战之前,先看一遍项目职责拆分](#8. 实战之前,先看一遍项目职责拆分)
- [9. 实战搭建:从零把链路跑通](#9. 实战搭建:从零把链路跑通)
- [9.1 第一步:浏览器只负责执行地图动作](#9.1 第一步:浏览器只负责执行地图动作)
- [9.2 第二步:Node 负责 MCP Server + WebSocket 桥接](#9.2 第二步:Node 负责 MCP Server + WebSocket 桥接)
- [9.3 第三步:Host 负责"模型 ↔ 工具"循环](#9.3 第三步:Host 负责“模型 ↔ 工具”循环)
- [9.4 第四步:把"终端输入"升级成"页面输入"](#9.4 第四步:把“终端输入”升级成“页面输入”)
- [10. 两种页面聊天方案对比:哪种是"真 MCP"](#10. 两种页面聊天方案对比:哪种是“真 MCP”)
- [11. 一次完整调用的分层理解](#11. 一次完整调用的分层理解)
- [11.4 把读者最容易追问的几个问题串起来理解](#11.4 把读者最容易追问的几个问题串起来理解)
- [12. 新手最容易踩的坑](#12. 新手最容易踩的坑)
- [13. 你可以如何扩展这套地图能力](#13. 你可以如何扩展这套地图能力)
- [14. 学完这篇教程后,你应该真正记住什么](#14. 学完这篇教程后,你应该真正记住什么)
1. 这篇教程要解决什么问题
很多人第一次做"大模型驱动地图"的时候,都会有一个非常自然的想法:
我在网页上放一个聊天框,用户输入"飞到上海",然后把这句话发给模型;模型决定调用工具;前端收到工具调用后,直接控制地图飞过去。
这个想法并不算错,而且它确实能跑。
但问题在于:这不一定是完整的 MCP 架构。
如果你后面想把地图能力做成:
- 可以被不同宿主统一调用
- 可以被大模型按工具方式自动发现
- 可以在前端、桌面、服务端之间分层复用
- 可以持续扩展成"通用地图执行器"
那你很快就会发现:
"前端直接请求模型并本地执行工具" ,和
"真正完整的 MCP 链路",其实是两套不同的设计思路。
这篇教程,就是要把这两件事讲清楚。
我们会先讲原理,再讲架构,最后用一个 Vue + Cesium + Node + MCP 的最小 demo 把整个链路跑起来。
2. 先别写代码:先搞懂两个很像但本质不同的方案
2.1 方案一:前端直连模型
这是最容易想到、也最容易快速跑通的一种方式。
它的基本流程是:
- 用户在网页输入"飞到上海"
- 前端把用户消息和工具定义一起发给模型
- 模型返回:要调用
flyToShanghai - 前端收到
tool_calls - 前端本地执行
flyToShanghai - 前端控制 Cesium 飞到上海
- 前端再把工具结果发回模型
- 模型生成最终回复
它的时序图长这样:
text
用户
-> 前端页面
-> 大模型 API
-> 前端页面(收到 tool_calls)
-> 前端本地工具执行
-> Cesium 地图
-> 前端页面
-> 大模型 API
-> 前端页面
-> 用户
这个方案的优点非常明显:
- 容易理解
- 容易开发
- 页面交互很直接
- 没有额外的 Host 层
但它也有一个根本特点:
前端同时承担了"聊天客户端"和"工具执行器"两种角色。
这就是后面很多结构混乱的起点。
2.2 方案二:真正完整的 MCP
完整 MCP 不是前端自己决定怎么调用工具,而是由一个 Host 来负责:
- 和用户对话
- 和模型交互
- 把工具提供给模型
- 在模型决定调用工具后,真正去调用 MCP Server
- 再把结果回灌给模型
在这个架构里,浏览器不再负责"模型调度",而只负责"地图动作执行"。
完整链路如下:
text
用户
-> Host
-> 模型
-> Host
-> Node MCP Server
-> 浏览器
-> Cesium
-> 浏览器
-> Node MCP Server
-> Host
-> 模型
-> Host
-> 用户
这条链路里最关键的一句是:
模型不直接调用 Node。Host 才是真正替模型调用工具的人。
这是理解 MCP 的核心。
2.3 它们最核心的区别
很多人会觉得:
"前端直连模型"和"完整 MCP"不都是模型调用工具吗?
表面上看都像"工具调用",但真正的区别在于:
| 维度 | 前端直连模型 | 完整 MCP |
|---|---|---|
| 谁和模型对话 | 前端页面 | Host |
| 谁拿到 tool_calls | 前端页面 | Host |
| 谁真正调工具 | 前端页面 | Host |
| Node 在哪里 | 可以没有 | 是 MCP Server |
| 浏览器扮演什么 | 聊天端 + 执行端 | 纯执行端 |
| 是否符合完整 MCP 分层 | 不一定 | 是 |
最通俗的说法是:
- 前端直连模型:页面自己又当指挥,又当工人
- 完整 MCP:Host 当指挥,浏览器只负责干活
3. 为什么很多人一开始会把两套方案混在一起
因为它们在表面上太像了。
无论是哪种方案,你都会看到类似这些词:
- tools
- tool_calls
- function calling
- flyToShanghai
- 地图飞行
于是就很容易误以为:
"我已经把 tools 发给模型了,模型也返回要调用的工具了,那我就是在做 MCP。"
实际上不一定。
真正的问题不是"有没有工具",而是:
谁在承担工具调度这件事。
如果是前端自己在做调度,那你更接近"前端直连模型 + 本地执行工具"。
如果是 Host 在调度,Node 暴露 MCP 工具,浏览器只执行动作,那才是完整 MCP。
4. 先建立整体认知:完整 MCP 里有哪些角色
在地图场景里,我们可以把系统拆成四个角色。
4.1 用户
负责说自然语言,比如:
- 飞到上海
- 把镜头拉近一点
- 在当前位置加一个标记
用户只关心结果,不关心内部链路。
4.2 Host
Host 是这套系统的大脑调度层。
它负责:
- 接收用户输入
- 请求大模型
- 把工具列表告诉模型
- 当模型决定调用工具时,真正去调用 MCP Server
- 把工具结果再喂回模型
- 把最终自然语言回复返回给用户
你可以把 Host 理解成:
"懂模型、懂工具、懂对话流程"的中控台。
4.3 MCP Server
MCP Server 负责把系统能力暴露成标准工具。
比如:
flyToShanghaiflyToLocationpingBrowseraddPointdrawPolyline
它不直接负责和用户聊天,也不负责渲染地图。
它只负责:
"我这里有这些工具,你要调哪个,我帮你转成真实动作。"
4.4 浏览器 + Cesium
浏览器里的地图页面不再和模型直接对话,而是只做一件事:
接收动作命令,并在地图里执行。
比如收到:
json
{
"method": "flyTo",
"params": {
"longitude": 121.4737,
"latitude": 31.2304,
"height": 8000
}
}
浏览器就调用 Cesium 相机飞行。
所以浏览器的角色是:
地图动作执行器。
5. 完整 MCP 的时序图:一句"飞到上海"是怎么穿过整个系统的
下面这张图,是整篇教程最重要的一张图。
text
┌──────┐ ┌───────────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌────────────┐ ┌─────────┐
│ 用户 │ │ MCP Host │ │ 模型 │ │ Node MCP │ │ WebSocket │ │ 浏览器前端 │ │ Cesium │
│ │ │ │ │ (LLM) │ │ Server │ │ 通道 │ │ + Bridge │ │ Scene │
└─┬────┘ └──────┬────────┘ └────┬─────┘ └──────┬───────┘ └────┬─────┘ └─────┬──────┘ └────┬────┘
│ "飞到上海" │ │ │ │ │ │
│──────────────────>│ │ │ │ │ │
│ │ 把用户消息发给模型 │ │ │ │ │
│ │──────────────────>│ │ │ │ │
│ │ │ 判断需要用工具 │ │ │ │
│ │<──────────────────│ tool_call: │ │ │ │
│ │ │ flyToShanghai │ │ │ │
│ │ 调 Node MCP 工具 │ │ │ │ │
│ │────────────────────────────────────>│ │ │ │
│ │ │ │ sendToBrowser() │ │ │
│ │ │ │─────────────────>│ │ │
│ │ │ │ │ JSON-RPC 消息 │ │
│ │ │ │ │───────────────>│ │
│ │ │ │ │ │ bridge.execute│
│ │ │ │ │ │──────────────>│
│ │ │ │ │ │ 相机飞到上海 │
│ │ │ │<─────────────────│ 执行结果 │ │
│ │<────────────────────────────────────│ tool result │ │ │
│ │ 再把结果发给模型 │ │ │ │ │
│ │──────────────────>│ │ │ │ │
│ │<──────────────────│ 返回自然语言回复 │ │ │ │
│<──────────────────│ │ │ │ │ │
这张图里最值得你盯住的两个点是:
5.1 模型并没有直接连 Node
模型只是返回:
我建议调用
flyToShanghai
真正去执行 callTool() 的,是 Host。
5.2 浏览器并不参与"思考"
浏览器只负责:
- 收消息
- 执行动作
- 回结果
它不负责选择工具,也不负责和模型多轮推理。
6. 为什么这个架构更适合地图场景
地图类应用和普通文本工具类应用有一个很大的区别:
地图动作通常必须在浏览器或图形环境里执行。
比如:
- 相机飞行
- 地物绘制
- 图层显示隐藏
- 交互式选点
- 实时动画
这些事情并不适合让 Node 自己去做。
Node 很适合做:
- 协议层
- 工具注册
- 连接管理
- 请求转发
- 结果聚合
浏览器很适合做:
- 渲染
- 动画
- 场景执行
- 与 Cesium 交互
所以最自然的分工就是:
- Host:负责智能调度
- Node MCP Server:负责能力暴露与转发
- 浏览器:负责地图执行
这比"前端自己包办一切"要更清晰,也更容易扩展。
7. 本教程的 demo 架构
我们用一套很小的 demo 来实现完整链路。
技术角色如下:
前端页面(Vue + Cesium)
负责:
- 初始化 Cesium
- 连接 Node 发来的 WebSocket 消息
- 收到动作后调用
MiniBridge.execute() - 真正控制地图飞行
MiniBridge
负责把通用动作转成 Cesium API。
比如:
flyTo->viewer.camera.flyTo(...)
Node index.ts
负责:
- 启动 WebSocket Server
- 启动 MCP Server
- 注册
flyToShanghai/pingBrowser - 收到工具调用后把动作转发给浏览器
Host
最初可以是终端版 host.ts,后面再升级成页面版 host-web.ts。
它负责:
- 和模型对话
- 获取 MCP 工具列表
- 调用 MCP 工具
- 把工具结果回灌给模型
8. 实战之前,先看一遍项目职责拆分
在真正开始编码之前,我们先把职责拆成三层。
第 1 层:执行层
执行层在浏览器里。
它只负责:
- 连接 WebSocket
- 执行收到的地图命令
- 返回结果
第 2 层:工具层
工具层在 Node 里。
它只负责:
- 注册工具
- 维护会话
- 把工具调用转发给浏览器
第 3 层:智能调度层
智能调度层在 Host 里。
它只负责:
- 和模型交互
- 决定是否调用工具
- 触发 MCP 调用
- 收尾生成自然语言回答
把职责拆干净以后,你会发现整个系统其实并不复杂。
复杂感大多来自于:
一开始把前端、模型、工具、地图执行全写在一个文件里。
9. 实战搭建:从零把链路跑通
下面开始进入真正的搭建过程。
9.1 第一步:浏览器只负责执行地图动作
这一层你可以理解成一个"地图机器人"。
它不思考,只执行。
一个非常典型的桥接器长这样:
js
class MiniBridge {
constructor(viewer) {
this.viewer = viewer
}
async execute(cmd) {
const action = cmd?.action
const params = cmd?.params || {}
switch (action) {
case 'flyTo':
await this.flyTo(params)
return { success: true }
default:
return { success: false, error: `Unsupported action: ${action}` }
}
}
flyTo(params) {
const { longitude, latitude, height = 8000 } = params
return new Promise((resolve) => {
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
complete: resolve,
})
})
}
}
这个桥的意义在于:
- 上层不用直接知道 Cesium API
- 上层只需要说"执行
flyTo" - 浏览器负责把它翻译成具体地图动作
这一步很关键,因为它让地图执行能力变成了一个可复用的动作接口。
9.2 第二步:Node 负责 MCP Server + WebSocket 桥接
这一步是整个架构的中间层。
Node 做两件事:
9.2.1 注册 MCP 工具
例如:
ts
server.tool(
'flyToShanghai',
'Fly camera to Shanghai in connected browser Cesium scene.',
{
height: z.number().default(8000),
duration: z.number().default(2),
},
async ({ height = 8000, duration = 2 }) => {
const result = await sendToBrowser('flyTo', {
longitude: 121.4737,
latitude: 31.2304,
height,
duration,
heading: 0,
pitch: -45,
})
return {
content: [{ type: 'text', text: JSON.stringify(result ?? { success: true }) }],
}
},
)
这里看起来像是在"飞到上海",但其实 Node 没有自己控制地图。
它做的只是:
- 把工具名称暴露给外界
- 收到工具调用时,转发一个
flyTo命令给浏览器
9.2.2 通过 WebSocket 把动作发给浏览器
核心逻辑是这样的:
ts
function sendToBrowser(method, params, timeoutMs = 15000) {
return new Promise((resolve, reject) => {
const ws = getBrowser(DEFAULT_SESSION)
if (!ws || ws.readyState !== WebSocket.OPEN) {
reject(new Error('No browser connected.'))
return
}
const id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
pendingRequests.set(id, { resolve, reject, timer })
ws.send(JSON.stringify({
jsonrpc: '2.0',
id,
method,
params,
}))
})
}
它的本质是:
把 MCP 工具调用转成浏览器可执行的动作消息。
9.3 第三步:Host 负责"模型 ↔ 工具"循环
这是完整 MCP 里最容易被忽略,但又最关键的一层。
Host 的工作流程是:
- 收到用户输入
- 请求模型
- 把工具列表发给模型
- 模型返回
tool_calls - Host 调用 MCP Server
- Host 拿到工具结果
- Host 再把结果发给模型
- 模型生成最终回复
这个过程可以抽象成这样:
ts
const tools = await client.listTools()
const first = await callChatCompletions(messages, openAITools)
const toolCalls = first?.choices?.[0]?.message?.tool_calls || []
for (const tc of toolCalls) {
const result = await client.callTool({
name: tc.function.name,
arguments: JSON.parse(tc.function.arguments || '{}'),
})
secondMessages.push({
role: 'tool',
tool_call_id: tc.id,
content: toolResultToText(result),
})
}
const second = await callChatCompletions(secondMessages, openAITools)
很多新手在这里第一次真正理解到:
Host 是连接"模型世界"和"工具世界"的那个人。
9.4 第四步:把"终端输入"升级成"页面输入"
一开始,为了最小验证链路,你可以用终端版 Host:
- 在终端输入"飞到上海"
- Host 去请求模型
- Host 再调用 MCP Server
这是最容易验证完整链路的方式。
但最终用户肯定希望在网页输入,而不是在终端输入。
这时,正确思路不是把"前端直连模型"改回来,而是:
给 Host 再包一层 HTTP 接口,让页面把聊天请求发给 Host。
于是链路就变成:
text
页面输入
-> Host Web API
-> 模型
-> Host Web API
-> Node MCP Server
-> 浏览器执行地图动作
-> Node MCP Server
-> Host Web API
-> 模型
-> 页面
这一步很重要,因为它保证了:
- 页面输入回来了
- 但完整 MCP 链路没有被破坏
也就是说,页面输入 和 完整 MCP 是可以同时成立的。
10. 两种页面聊天方案对比:哪种是"真 MCP"
到了这里,很多人会问:
"那页面聊天到底有几种实现方式?"
答案是两种。
10.1 方式 A:页面直接请求模型
text
页面
-> 模型
-> 页面收到 tool_calls
-> 页面本地执行工具
-> 页面再请求模型
这种方式好处是简单,但页面承担了太多职责。
10.2 方式 B:页面请求 Host Web API
text
页面
-> Host Web API
-> 模型
-> Host Web API
-> MCP Server
-> 浏览器地图执行
-> Host Web API
-> 模型
-> 页面
这种方式更符合完整 MCP,因为页面没有直接参与工具调度。
一句话判断标准
如果页面自己在处理 tool_calls,那通常不是完整 MCP。
如果页面只是把用户输入交给 Host,然后等待回复,那更接近完整 MCP。
11. 一次完整调用的分层理解
当用户在页面输入"飞到上海"时,可以从三个层面来看这件事。
11.1 语义层
模型在理解:
- 用户想去哪里
- 是否需要调用工具
- 应该调用哪个工具
11.2 工具层
Host 和 MCP Server 在处理:
- 工具发现
- 工具参数
- 工具调用
- 结果返回
11.3 执行层
浏览器和 Cesium 在处理:
- 相机控制
- 地图飞行
- 动画执行
- 场景渲染
你把这三层分开看,就会觉得整个系统非常清晰。
11.4 把读者最容易追问的几个问题串起来理解
在真实学习过程中,很多人不会一下子就被"架构图"讲明白,反而会在写 demo、跑代码、看日志的时候不断冒出一些非常具体的问题。
这些问题看起来零散,但其实正好能帮助你把完整 MCP 理解得更扎实。
下面把几个最典型的问题串起来讲。
问题一:"大模型和 Node 这条链路到底怎么连起来的?"
这是最常见的困惑。
很多人第一次看到 index.ts 里的 McpServer,会本能地以为:
模型是不是直接连到了这个 Node 服务?
其实不是。
更准确的链路是:
text
用户 -> Host -> 模型 -> Host -> Node MCP Server -> 浏览器 -> Cesium
也就是说:
- 模型只负责"决定要不要调用工具"
- Host 才是"真正去调用 MCP 工具的人"
- Node MCP Server 只是"被调用的工具服务"
- 浏览器只是"执行地图动作的地方"
所以真正连模型和 Node 的,不是模型自己,而是 Host。
问题二:"host-web.ts 里没有把工具传给 StdioClientTransport,那工具是怎么来的?"
这也是一个特别典型的误解。
很多新手看到这段代码时,会以为:
ts
const transport = new StdioClientTransport({
command: getNpxCommand(),
args: ['tsx', 'index.ts'],
})
是不是已经把工具传进去了?
其实没有。
这里的意思只是:
- 用
tsx index.ts启动一个 MCP Server 进程 - 再让 Host 通过 stdio 和这个进程通信
args 不是工具内容本身,它只是告诉 transport:去启动哪一个 Server。
真正的工具读取发生在后面:
ts
await client.connect(transport)
const toolsRes = await client.listTools()
toolsCache = toolsRes.tools || []
openAITools = toolsCache.map(mcpToolToOpenAITool)
你可以把这几步理解成:
args:找到餐厅地址connect:走进餐厅listTools:向餐厅要菜单openAITools:把菜单整理成模型能看懂的格式
所以工具并不是被"静态读取源码"拿到的,而是 index.ts 运行起来以后,通过 server.tool(...) 向外暴露,再由 Host 用 listTools() 动态读取出来的。
问题三:"后面看起来不是和前端直连一样吗?不也还是把 tools 发给模型吗?"
这个问题问得非常好,因为它正好点出了"哪里像、哪里不一样"。
答案是:
在模型接口这一层,确实很像;在系统架构这一层,本质上不一样。
两种方案后面都会做这件事:
text
messages + tools -> model -> tool_calls
所以从模型的视角看,它们很像。
但系统架构上差别很大:
前端直连模型
- 工具定义在前端
- 工具由前端发给模型
- 模型返回
tool_calls - 前端本地执行工具
完整 MCP
- 工具定义在 MCP Server
- Host 用
listTools()动态发现工具 - Host 把工具发给模型
- 模型返回
tool_calls - Host 再用
callTool()调 MCP Server - 浏览器只负责执行动作
所以更准确地说:
完整 MCP 不是"不把工具发给模型",而是把"工具定义权"和"工具执行调度权"从前端抽离了出来。
问题四:"既然终端里输入能飞到上海,为什么还要做页面输入?"
这是从"验证链路"过渡到"真正产品形态"的关键一步。
一开始用终端输入,是为了验证最小闭环:
text
终端输入 -> Host -> 模型 -> Host -> MCP Server -> 浏览器 -> Cesium
当这条线通了以后,你已经证明:
- Host 没问题
- MCP Server 没问题
- 浏览器执行链没问题
这时候再把"终端输入"换成"页面输入",本质上不是重新发明一套逻辑,而是把 Host 的入口从命令行换成 HTTP API:
text
页面输入 -> host-web.ts -> 模型 -> host-web.ts -> MCP Server -> 浏览器 -> Cesium
所以页面输入版的核心不是"让前端重新直连模型",而是:
页面把用户输入交给 Host,由 Host 继续完成完整 MCP 链路。
问题五:"我到底该怎么判断自己现在是不是在走完整 MCP?"
这个问题非常重要,因为很多 demo 表面上看都叫"工具调用",但架构上完全不是一回事。
最实用的判断标准只有一句:
看工具定义写在哪里,工具调度由谁负责。
如果是这样:
- 前端自己写
getTools() - 前端自己发给模型
- 前端自己执行
runLocalTool()
那本质上还是前端直连。
如果是这样:
index.ts里server.tool(...)定义工具- Host 通过
listTools()发现工具 - Host 通过
callTool()调用工具 - 浏览器只负责执行动作
那才是完整 MCP。
你可以把这个判断标准记成一句话:
前端直连看的是"页面会不会调工具",完整 MCP 看的是"工具是不是独立存在于 MCP Server 里"。
问题六:"为什么我一开始总觉得两套方案混在一起?"
因为在学习初期,大家最先看到的都是这些显眼的词:
- tools
- tool_calls
- flyToShanghai
- bridge.execute
- WebSocket
这些词在两套架构里都出现,所以很容易产生一种错觉:
"我都在用 tools 了,那我就是在做 MCP。"
真正需要区分的,其实不是关键词,而是角色分工:
- 谁是 Host
- 谁是 MCP Server
- 谁是浏览器执行器
- 谁才是真正的工具源头
一旦你把角色看清楚,之前那些零散的疑问就会突然全部串起来。
你会发现,上面这些问题并不是无关的"零碎提问",而是学习过程中非常自然的一条理解升级路径:
- 先困惑模型和 Node 的关系
- 再困惑工具是怎么被发现的
- 再进一步意识到"为什么看起来和前端直连很像"
- 最后才真正明白:完整 MCP 的价值,不在"有没有 tools",而在工具定义、工具调度和动作执行被拆成了独立角色
12. 新手最容易踩的坑
12.1 把"tool calling"误以为就是 MCP
不是的。
tool calling 是模型返回工具调用决策。
MCP 是一整套 client-server 协议和角色分层。
12.2 让浏览器既当聊天端又当执行端
短期能跑,长期一定会乱。
因为你会发现:
- 页面越来越重
- 工具扩展越来越难
- 复用性越来越差
12.3 误以为模型直接调用 Node
模型不会自己去连 Node。
真正调用 Node MCP 工具的是 Host。
12.4 忘了浏览器必须先连上 WebSocket
如果浏览器没有连上,Node 收到工具调用也没法执行地图动作。
12.5 同时起了两个 Server,导致端口冲突
比如 9100 已经被占用,再起一个同样监听 9100 的进程,就会报端口占用错误。
13. 你可以如何扩展这套地图能力
当"飞到上海"跑通以后,这套架构就已经具有扩展性了。
你可以继续加这些工具:
13.1 通用飞行类
flyToLocation({ longitude, latitude, height })setCameraView(...)lookAt(...)
13.2 地图绘制类
addPoint(...)addPolyline(...)addPolygon(...)
13.3 图层控制类
showLayer(...)hideLayer(...)toggleLayer(...)
13.4 场景查询类
getCurrentCamera()getSelectedEntity()pingBrowser()
13.5 复杂业务类
- 无人机巡航
- 路线规划
- 智慧城市场景切换
- 事件定位与高亮
这时候你会真正体会到完整 MCP 架构的价值:
你扩展的是"工具能力",而不是把业务越写越塞进页面里。
14. 学完这篇教程后,你应该真正记住什么
如果你只记住一句话,请记这句:
完整 MCP 不是"模型自己调地图",而是"Host 根据模型决策去调工具,再让浏览器执行地图动作"。
如果你再多记三句,那就是:
- 前端直连模型 能跑,但不等于完整 MCP
- Host 是"模型和工具之间的真正调度者"
- 浏览器 在地图场景里最适合做执行器,而不是全能中枢
最后,把两套方案再浓缩成最简版本:
前端直连模型
text
页面自己又当聊天端,又当工具执行端
完整 MCP
text
Host 负责调度,Node 负责工具,浏览器负责执行
这就是这篇教程想真正教会你的东西。
结语
从"让地图飞到上海"这件小事出发,你其实已经接触到了一个很有代表性的工程问题:
当大模型开始接管工具调用时,系统应该怎么分层,才不会越做越乱?
地图场景给了我们一个很典型的答案:
- 把思考留给模型
- 把调度交给 Host
- 把能力暴露给 MCP Server
- 把渲染执行留给浏览器
这样搭起来的系统,才真正适合继续长大。
如果你已经把"飞到上海"跑通了,那么下一步,不是继续把代码堆在页面里,而是:
把更多地图能力,整理成一套干净的 MCP 工具体系。
这时,你就真正迈进了"通过大模型聊天驱动地图系统"的工程化阶段。