
1. 从「约饭选址」到 AI 地图应用WorkBuddy 腾讯位置服务 MCP 到底能做什么先说我自己的真实起点。去年冬天组织一次四人聚会张三在回龙观、李四在东直门、王五在南四环、我在中关村群里刷了三十多条「在哪见」最后选了个离谁都远的地方。传统导航只回答「怎么去」不回答「去哪最公平」。这个缺口就是 AI 地图应用的机会让用户用一句自然语言描述需求由 AI 完成意图识别、调用地理编码与路线规划工具、再把结果画到地图上。腾讯位置服务开发者征文大赛这次的主题正好卡在这个点上。腾讯位置服务提供 geocoder地理编码、placeSearchNearby周边 POI 检索、directionDriving驾车路线、matrix多点距离矩阵等 WebService 能力以及 JavaScript API GL 的 3D 渲染与 MultiMarker、MultiPolyline 图层WorkBuddy 负责把模糊想法翻译成架构、生成可运行代码、整理文档。两者之间用 MCPModel Context Protocol串起来AI Agent 就能像人一样「先想再调工具」。这篇写给谁会一点 HTML/JS、想做一个能跑起来的 AI 地图 Demo 的开发者正在准备征文大赛、需要可复制配置和联调步骤的参赛者以及想把 MCP 真正用进业务、而不是停在概念演示的人。全文按「问题场景 → 前置准备 → 可复制配置 → 验证请求 → 报错排查 → 下一步」推进每一步都给命令、参数和预期结果你可以边看边敲。我试过把 MCP 当噱头堆 API结果页面很花但没人用后来把「汇合点计算」这一个场景做透反而被朋友追着要链接。所以下面的路线是先跑通一条最小链路再往上加功能。2. 前置准备TaoToken 接入与腾讯位置服务 Key 申请全流程2.1 为什么中间要有一层模型网关MCP 的调用方是 AI AgentAgent 需要一个能稳定输出结构化 tool_calls 的模型。直接在前端硬编码模型厂商的 Key一是暴露风险二是换模型要改代码。用 TaoToken 这类兼容 OpenAI 协议的中转层好处是 Base URL 统一、模型 ID 可切换、额度集中管理前端只认一个地址。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 注意这个地址不加 UTM 参数配置里写干净版本。2.2 拿到三件套Base URL、Key、Model ID登录后进控制台在 API Keys 页面创建一个新 Key。建议按项目命名比如map-agent-demo方便后面按项目看用量。创建后立刻复制页面刷新就不再完整显示。模型 ID 的选择上做 MCP Tool Calling 要挑支持 function calling 的模型。行程规划这种多步推理场景建议用推理能力强的型号如果只是做地址解析和简单问答轻量型号就够成本差好几倍。具体可用列表在模型对话页面能看到也可以直接在控制台里试跑一条请求验证。2.3 腾讯位置服务侧的 Key 与安全设置去腾讯位置服务控制台创建应用分别申请两类 Key一类给 JavaScript API GL前端渲染用一类给 WebServicegeocoder、direction 等后端接口用。前端 Key 必须配 Referer 白名单把本地调试域名和部署域名都加进去否则浏览器控制台会直接报INVALID_USER_DOMAIN。配额管理别偷懒。WebService 的每日调用上限设一个合理值比如 5000 次防止 Demo 被爬。纯前端架构下 Key 一定会出现在网络请求里这是参赛 Demo 可以接受的取舍正式上线时用云函数做一层代理把 WebService Key 藏到服务端。2.4 本地环境与目录结构Node 18 以上一个静态服务器npx serve或 VSCode Live Server 都行。目录建议这样分map-agent-demo/ ├── index.html # 地图容器 对话面板 ├── js/ │ ├── mcp-client.js # MCP 调用封装 │ ├── agent.js # 意图识别 tool_calls 编排 │ └── map-render.js # GL 图层渲染 ├── config/ │ └── settings.json # 模型与 Key 配置勿提交仓库 └── prompts/ └── system.md # WorkBuddy 提示词模板把配置单独放一个文件后面换模型、换 Key 只改一处。.gitignore里加上config/settings.json。3. 可复制配置MCP Server、settings.json 与 WorkBuddy 提示词模板3.1 MCP Server 配置片段MCP 的配置格式各客户端略有差异核心是三件套命令、参数、环境变量。下面这份是通用形态路径按你本地实际位置改{ mcpServers: { tencent-map: { command: npx, args: [-y, tencentmap/mcp-server], env: { TENCENT_MAP_KEY: 你的WebService-Key, TENCENT_MAP_BASE_URL: https://apis.map.qq.com } } } }如果你用的是支持 TOML 的客户端等价写法[mcp_servers.tencent-map] command npx args [-y, tencentmap/mcp-server] [mcp_servers.tencent-map.env] TENCENT_MAP_KEY 你的WebService-Key TENCENT_MAP_BASE_URL https://apis.map.qq.com注意TENCENT_MAP_KEY用的是 WebService 类型 Key不是前端 GL 的 Key两者权限不同混用会报INVALID_KEY。3.2 settings.json模型侧配置{ baseUrl: https://taotoken.net/api, apiKey: sk-你的TaoToken-Key, model: 你的模型ID, temperature: 0.3, tools: [ geocoder, placeSearchNearby, directionDriving, directionWalking, matrix ] }temperature压到 0.3 是因为工具调用需要稳定输出 JSON太高会偶发格式错乱。tools数组显式声明允许调用的工具避免模型幻觉出不存在的函数名。3.3 WorkBuddy 提示词模板把下面这段存进prompts/system.md作为 Agent 的系统提示你是「聚点智行」地图助手负责把用户的自然语言需求转成地图工具调用。 工作流程 1. 解析意图归类为汇合点计算 / 周边搜索 / 路线规划 / 行程编排 2. 从文本中抽取地点、时间、人数、预算等约束 3. 选择工具地名转坐标用 geocoder找周边用 placeSearchNearby 算路线用 directionDriving 或 directionWalking多点距离用 matrix 4. 每次调用前用一句话说明理由调用后解读返回结果 5. 最终输出结构化行程单含时间、地点、费用、交通方式 约束 - 坐标统一用「纬度,经度」格式保留 6 位小数 - 不确定的地点先调 geocoder 确认不要猜坐标 - 费用估算标注「预估」不要编造精确价格这份模板的关键是第 4 条要求 AI 输出调用理由。这既是给用户看的「思维链」也是你调试时的日志。3.4 前端调用封装// js/mcp-client.js const settings await fetch(/config/settings.json).then(r r.json()); export async function callModel(messages, tools) { const res await fetch(${settings.baseUrl}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${settings.apiKey} }, body: JSON.stringify({ model: settings.model, messages, tools, temperature: settings.temperature }) }); if (!res.ok) throw new Error(模型请求失败: ${res.status}); return res.json(); }这段是整条链路的入口后面所有工具调用都由它返回的tool_calls字段驱动。4. 验证请求从地理编码到行程单的完整联调4.1 第一步验证模型连通性先用一条最简单的请求确认 Base URL 和 Key 没问题curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-你的Key \ -d { model: 你的模型ID, messages: [{role: user, content: 回复 OK 两个字母}] }返回体里choices[0].message.content是OK说明模型侧通了。如果这里就失败先别往下走去第 5 节排查。4.2 第二步验证 geocoder 工具调用给模型发一条带地名的请求看它是否主动发起 tool_callconst messages [ { role: system, content: systemPrompt }, { role: user, content: 西直门在哪给我坐标 } ]; const result await callModel(messages, toolDefinitions); console.log(result.choices[0].message.tool_calls);预期输出里能看到function.name为geocoderarguments是{address:西直门}。拿到这个 tool_call 后你把它转发给腾讯位置服务的 geocoder 接口返回的坐标大约是39.9434,116.3497。把结果作为role: tool的消息塞回对话模型会生成一句自然语言解读。4.3 第三步跑通「四人汇合点」场景这是最能体现 MCP 价值的场景。用户输入「帮 4 个人找汇合点分别在回龙观、东直门、南四环、中关村」Agent 的执行序列是先对四个地名各调一次 geocoder拿到四组坐标再用 matrix 接口一次性计算四点两两之间的距离矩阵然后按「距离总和最小」选出候选点用 placeSearchNearby 在候选点周边 500 米内找餐厅或咖啡厅作为具体汇合地最后用 MultiMarker 把四个起点和一个汇合点画到地图上用 MultiPolyline 连出四条路线。matrix 接口的调用参数长这样const matrixResult await fetch( https://apis.map.qq.com/ws/distance/v1/matrix?modedriving from${origins.join(;)}to${destinations.join(;)} key${webServiceKey} ).then(r r.json());返回的result.rows是二维数组rows[i].elements[j].distance就是第 i 个起点到第 j 个终点的距离。遍历所有候选点求总和取最小值即可。4.4 第四步地图渲染与结果确认拿到汇合点坐标后初始化 GL 地图const map new TMap.Map(document.getElementById(map), { center: new TMap.LatLng(39.9434, 116.3497), zoom: 12, pitch: 45, viewMode: 3D }); const markerLayer new TMap.MultiMarker({ map, styles: { start: new TMap.MarkerStyle({ width: 24, height: 32, src: pin-blue.png }), meet: new TMap.MarkerStyle({ width: 28, height: 36, src: pin-red.png }) }, geometries: [ { id: p1, styleId: start, position: new TMap.LatLng(40.0755, 116.3390) }, { id: meet, styleId: meet, position: new TMap.LatLng(39.9434, 116.3497) } ] });验证清单地图能加载并显示 3D 视角四个起点标记和一个汇合点标记都出现点击标记弹出 InfoWindow 显示地名和距离路线连线颜色区分不同出行方式控制台无INVALID_USER_DOMAIN报错。4.5 第五步行程编排场景把「西直门一日行程」这类复杂需求丢进去观察 Agent 是否按「解析约束 → 搜住宿 → 排三餐 → 选景点 → 算总距离 → 输出行程单」的顺序执行。一个健康的执行链路会产生 8 到 12 次工具调用每次调用之间模型会输出一句推理说明。如果模型跳过 geocoder 直接猜坐标说明提示词里的约束没生效回去检查prompts/system.md是否被正确加载。5. 常见报错排查401、local proxy failed 与 tool_calls 解析失败5.1 401 Unauthorized最常见的原因是 Key 复制时带了空格或换行。用echo -n sk-xxx | wc -c确认长度或者直接在代码里console.log(apiKey.length)。另一个原因是把腾讯位置服务的 Key 填到了模型配置里两者前缀不同别搞混。如果确认 Key 正确仍报 401检查请求头是不是Authorization: Bearer sk-xxx少个空格也会失败。5.2 local proxy failed这个报错通常出现在 MCP Server 启动阶段含义是客户端连不上本地起的 MCP 进程。排查顺序先确认npx -y tencentmap/mcp-server能在终端单独跑起来再检查env里的TENCENT_MAP_KEY是否传进去了很多客户端不会自动继承 shell 环境变量最后看端口是否被占用换个端口重试。如果是 Windows 环境command字段可能要写npx.cmd而不是npx这是路径解析差异导致的。5.3 reading choices of undefined这个报错说明你在解析响应时result.choices是 undefined。三种可能请求根本没成功返回的是错误对象返回体结构和你预期的不一样先console.log(JSON.stringify(result))看原始结构流式响应没处理完就取值了加个await或改用非流式模式调试。调试期建议先关掉 streaming拿到完整响应再开。5.4 OAuth 相关报错部分 MCP 客户端在首次连接时会走 OAuth 授权流程。如果报OAuth callback failed检查回调地址是否和客户端注册的一致本地调试常用http://localhost:端口/callback。有些客户端把 token 缓存在用户目录下缓存损坏时删掉重新授权即可。5.5 tool_calls 格式错乱模型返回的arguments不是合法 JSON通常是 temperature 太高或提示词里没强调格式。把 temperature 降到 0.2 以下并在系统提示里加一句「工具参数必须是合法 JSON不要包含注释」。如果模型持续输出带 markdown 代码块的 JSON在解析前先剥掉json和。5.6 地图不显示或白屏先看浏览器控制台有没有INVALID_USER_DOMAIN有就是 Referer 白名单没配。没有报错但地图空白检查容器 div 有没有设高度GL 地图需要一个非零高度的父容器。3D 视角下如果显卡驱动老旧viewMode: 3D可能渲染失败临时改成2D验证是不是渲染层问题。6. 下一步把 Demo 变成能用的智能出行助手跑通上面这条链路后你手里已经有一个能对话、能调工具、能画地图的最小闭环。接下来可以往三个方向加厚。第一是补全工具集。目前只用了 geocoder、placeSearchNearby、direction、matrix 四类腾讯位置服务还有逆地理编码、IP 定位、行政区划等能力接进来能让 Agent 处理「我在哪」「这个坐标属于哪个区」这类问题。第二是做结果缓存。同一批地名反复调 geocoder 是浪费配额用 localStorage 或内存 Map 缓存「地名 → 坐标」的映射命中就直接返回。行程规划场景里用户改一个约束条件往往只需要重算部分节点缓存能显著降低延迟。第三是把 Key 挪到服务端。参赛 Demo 用纯前端可以接受但如果想给别人长期用用云函数包一层代理前端只调自己的接口WebService Key 不出现在浏览器里。这一步做完项目就从「演示」变成了「能上线」。如果你在编码 Agent 或长期跑自动化任务可以看看 Coding Plan 的额度方案只是验证模型和工具调用是否通模型对话页面直接试最快Key 管理和用量查看都在 API Keys 页面。接入过程中卡在某个报错接入文档里有各接口的完整参数说明对照着改通常能解决。地图应用的下半场拼的不是谁画的点更多而是谁能让 AI 真正理解「用户想去哪、和谁去、什么时候去」。把这条 MCP 链路跑顺你就已经站在起跑线前面了。