MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
传统的 CAD 地图应用都是由人来操作的,用户需要学习复杂的 CAD 操作命令和地图管理流程,操作成本相对较高。如果能让 AI 来帮我们操作地图,是不是就非常方便?
如果所有 CAD 地图应用都能做智能化改造,让 AI 能够理解和操作地图数据,每个人都拥有一个虚拟的"地图智能助理",我们只需要聊天一样跟地图智能助理"聊天",用自然语言提出我们的地图需求,智能助理会自动帮我们操作地图应用,实现我们的需求,这将大大提升人们在地图处理、CAD 图纸分析、空间数据管理等方面的工作效率和体验!
唯杰地图
基于 MCP 协议实现了 AI 代替人操作 CAD 地图应用这项技术,并应用到我们的唯杰地图引擎中,实现了唯杰地图系统的智能化改造。使用 MCP协议工具,天然地支持被 AI 识别和操控,包括地图浏览、CAD 图纸查询、空间数据提取、图形绘制等操作。同时还提供了一套前端 SDK,支持现有地图业务快速接入 AI、实现智能化,目前支持 Vue、React、Angular 等前端框架。
由于是基于标准的 MCP 协议实现的,具备通用性和广泛的适用性,可以通过各种不同类型的 MCP Host 来控制地图应用,比如可以通过网页上的 AI 对话框来控制地图应用,也可以通过Tace
、Cursor
等 IDE 工具,或者通过 Dify
、Coze
等智能体平台来操控,甚至可以通过手机App、微信小程序等方式远程遥控你的地图应用。我们可以与 AI 对话,让 AI 帮我们操作各类地图应用,实现我们的地图相关需求。
效果展示:
唯杰地图云端管理平台 https://vjmap.com/app/cloud/#/
点击界面左上角的MCP地址,可查看此次会话的MCP地址
可复制当前会话的MCP地址
到其他MCP客户端
如cursor
、trae
或cherry studio
中对当前地图进行提问
如在cursor
中通过MCP地址调用地图相关操作
MCP架构原理与流程
唯杰地图MCP系统采用服务端-前端分离架构,通过MCP(Model Context Protocol)协议实现双向通信。系统支持服务端工具和前端工具的混合调用,实现复杂的地图操作和交互功能。
整体架构
后端MCP工具
工具名称 | 分类 | 类型 | 工具描述 | 主要参数 |
---|---|---|---|---|
listmaps | map | 后端 | 获取地图列表信息,支持获取所有地图或指定地图的版本信息 | mapid (地图ID)、version (版本号)、mapIds (地图ID数组)、workspace (工作区)、pagination (分页)、curPage (当前页)、pageCount (每页数量) |
createMap | drawing | 后端 | 新建地图工具,支持两种创建方式:1、通过fileid和mapid创建地图;2、通过组合成新地图API创建 | createType (创建类型)、mapid (地图ID)、fileid (文件ID)、uploadname (文件名)、geom (几何渲染)、sourceMapid (源地图ID)、sourceVersion (源版本)、sourceClipbounds (裁剪范围)、fourParameter (四参数)、layers (图层列表)、workspace (工作区) |
deletemap | delete | 后端 | 删除地图功能,支持删除指定版本或所有版本的地图。重要:删除操作不可逆,请谨慎使用! | mapid (地图ID)、version (版本号)、retainVersionMaxCount (保留版本数)、workspace (工作区) |
getMapImage | map | 后端 | 根据范围和要显示的图层列表获取地图图像,返回二进制图片PNG格式。支持指定图层显示/隐藏,自动计算图片尺寸比例 | mapid (地图ID)、version (版本号)、bounds (范围)、pictureWidth (图片宽度)、layerOn (显示图层)、layerOff (隐藏图层)、backgroundColor (背景色)、transparent (透明背景)、styleName (样式名)、workspace (工作区)、returnBase64 (返回base64) |
fullSearch | query | 后端 | CAD图全文搜索文档,支持在CAD图纸中搜索文字内容、图层、块名称等信息 | query (搜索关键词)、map_ver (地图版本)、workspace (工作区)、type (文档类型)、bounding_box (范围)、time_range (时间范围)、limit (结果数量)、offset (偏移量)、fields (返回字段)、facet (聚合信息) |
queryFeatures | query | 后端 | 查询地图实体,支持条件查询、矩形范围查询、点查询和表达式查询四种查询类型 | mapid (地图ID)、version (版本号)、queryType (查询类型)、sql (SQL语句)、bounds (范围)、point (点坐标)、radius (半径)、expression (表达式)、workspace (工作区) |
extractTable | query | 后端 | 自动提取CAD图纸中的表格数据,支持指定坐标范围或全图提取。当需要查询图纸相关信息时,应优先调用此工具提取表格数据进行分析,可获取图纸中相关结构化数据信息 | mapid (地图ID)、version (版本号)、bounds (提取范围)、workspace (工作区) |
sqlDocHelper | query | 后端 | 获取查询地图实体的SQL编写文档和表结构说明,为AI生成SQL语句提供参考 | 无参数 |
createDwgDocHelper | drawing | 后端 | 获取创建图文档的参数说明和实体类型定义,为AI生成图形实体JSON数组提供参考 | 无参数 |
前端工具
工具名称 | 分类 | 类型 | 工具描述 | 主要参数 |
---|---|---|---|---|
map_get_info | map | 前端 | 获取当前地图的详细信息,包括缩放级别、中心点、旋转角度、倾斜角度、显示范围、地图参数、图层信息和工作区名称、缩略图地址等。返回的坐标为CAD坐标系统。 | 无参数 |
map_open_or_switch | map | 前端 | 打开或切换图功能 - 根据mapid和version参数打开指定的图。打开成功后会自动更新地图上下文数据到后台。 | mapid (地图ID)、version (版本号)、isKeepOldLayers (保留旧图层)、isVectorStyle (矢量样式)、isSetCenter (设置中心)、isFitBounds (适应范围)、layeron (开启图层)、layeroff (关闭图层)、backcolor (背景色) |
map_view_control | map | 前端 | 地图视图控制工具 - 支持缩放、平移、旋转、俯仰、缩放至地图范围等操作。所有坐标参数使用CAD坐标(x,y) | operation (操作类型)、zoom (缩放级别)、center (中心点)、bearing (旋转角度)、pitch (俯仰角度)、bounds (范围)、duration (动画时长) |
map_execute_code | runcode | 前端 | 执行自定义JavaScript代码,用于复杂的地图操作和自定义功能实现(需要唯杰地图帮助文档MCP一起使用) | code (JavaScript代码)、description (代码描述) |
map_draw_geojson | draw | 前端 | 根据GeoJSON数据绘制要素,支持所有标准GeoJSON几何类型。坐标使用CAD坐标系,样式属性在properties中配置 | geojsonData (GeoJSON数据,包含几何对象和样式属性) |
map_delete_drawn_features | draw | 前端 | 删除绘制的要素,支持删除指定实例或清空所有绘制图层 | instanceId (实例ID)、clearAll (清空所有) |
map_create_markers | draw | 前端 | 根据GeoJSON数据创建一个或多个标记,每个标记的样式配置从其properties中读取,支持自定义样式、拖拽、弹窗等功能 | geojsonData (GeoJSON数据,包含Point几何和标记配置属性) |
map_delete_markers | draw | 前端 | 删除地图上的标记,支持删除指定标记或清空所有标记 | markerId (标记ID)、clearAll (清空所有) |
用户可通过SDK自定义业务方面的MCP工具
MCP URL地址组成
VJMap MCP系统的URL地址支持工具过滤功能,可以通过URL参数来控制可用的工具分类和具体工具。
URL格式
/ai/mcp?sessionId={sessionId}&include_categories={categories}&exclude_categories={categories}&include_tools={tools}&exclude_tools={tools}&token={token}
官方默认mcp地址为
https://vjmap.com/server/ai/mcp?sessionId={前端每次会话的sessionID}&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M
如果只需要后端MCP工具,无需与前端交互sessionId参数可不填写。如
https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M
参数说明
参数名 | 类型 | 必填 | 描述 | 示例值 |
---|---|---|---|---|
sessionId | string | 是 | 会话ID,用于标识当前会话 | session_123456 |
token | string | 是 | 访问令牌,用于身份验证 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
include_categories | string | 否 | 包含的工具分类,多个分类用逗号分隔 | query,map,drawing |
exclude_categories | string | 否 | 排除的工具分类,多个分类用逗号分隔 | delete,utility |
include_tools | string | 否 | 包含的具体工具名称,多个工具用逗号分隔 | listmaps,fullSearch |
exclude_tools | string | 否 | 排除的具体工具名称,多个工具用逗号分隔 | deletemap |
工具分类
系统支持以下工具分类:
分类名称 | 分类值 | 描述 | 包含的工具 |
---|---|---|---|
地图管理 | map | 地图的基本CRUD操作 | listmaps , getMapImage , map_get_info , map_open_or_switch , map_view_control |
数据查询 | query | 数据查询和搜索相关 | fullSearch , queryFeatures , extractTable , sqlDocHelper |
图形处理 | drawing | 图形创建和处理相关 | createMap , createDwgDocHelper , map_draw_geojson , map_create_markers |
删除工具 | delete | 删除地图相关 | deletemap |
绘图工具 | draw | 前端绘图相关 | map_draw_geojson , map_delete_drawn_features , map_create_markers , map_delete_markers |
运行代码 | runcode | 代码执行相关 | map_execute_code |
前端自定义分类 | custom | 前端自定义分类 |
使用示例
示例1:只包含查询和地图工具
/ai/mcp?sessionId=session_123456&include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
说明: 只启用查询类工具(fullSearch
, queryFeatures
, extractTable
, sqlDocHelper
)和地图类工具(listmaps
, getMapImage
, map_get_info
, map_open_or_switch
, map_view_control
)
示例2:排除删除工具
/ai/mcp?sessionId=session_123456&exclude_categories=delete&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
说明: 启用除删除类工具外的所有工具,确保用户无法执行删除操作
示例3:只包含特定工具
/ai/mcp?sessionId=session_123456&include_tools=listmaps,fullSearch,map_get_info&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
说明: 只启用三个特定工具:listmaps
(获取地图列表)、fullSearch
(全文搜索)、map_get_info
(获取地图信息)
示例4:排除特定工具
/ai/mcp?sessionId=session_123456&exclude_tools=deletemap,map_execute_code&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
说明: 启用所有工具,但排除deletemap
(删除地图)和map_execute_code
(执行代码)这两个可能有安全风险的工具
示例5:组合使用包含和排除
/ai/mcp?sessionId=session_123456&include_categories=query,map&exclude_tools=deletemap&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
说明: 只启用查询和地图类工具,但进一步排除deletemap
工具,提供更精细的控制
注意事项
- 优先级规则:
include_tools
和exclude_tools
的优先级高于include_categories
和exclude_categories
- 参数冲突: 如果同一个工具既在
include_tools
中又在exclude_tools
中,exclude_tools
优先 - 空值处理: 如果某个参数为空或未提供,系统会使用默认行为(启用所有工具)
- 安全性: 建议在生产环境中使用
exclude_categories=delete
或exclude_tools=deletemap
来防止意外删除操作 - URL编码: 如果参数值包含特殊字符,请进行URL编码
在cursor
中加入MCP地址进行问答,效果如下
{"mcpServers": {"vjmap": {"url": "https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M"}}
}
如果需要与前端交互时,可在AI对话框中MCP工具中复制MCP地址。
{"mcpServers": {"vjmap": {"url": "https://vjmap.com/server/ai/mcp?sessionId=session-6323558e&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MSwiVXNlcm5hbWUiOiJyb290MSIsIk5pY2tOYW1lIjoicm9vdDEiLCJBdXRob3JpdHlJZCI6InJvb3QiLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjoxOTQyMzg5NTc0LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTYyNzAyODU3NH0.l1pP9FXu6ARDaaa-6ma0lp7ftbIk2t6rgmSmTqXry10"}}
}
注:每次会话的sessionId是不同的,上面的地址应该根据当前的会话来生成
MCP 前端SDK及以自定义工具示例
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>vjmap demo</title><link rel="stylesheet" type="text/css" href="https://vjmap.com/server/_demo/js/vjmap/vjmap.min.css"><script type="text/javascript" src="https://vjmap.com/server/_demo/js/vjmap/vjmap.min.js"></script>
</head><body style=" margin: 0;overflow: hidden;background-color:white;font-size: 16px"><div id="map" style="left:0;right:0;top:0;bottom:0;position: absolute;z-index: 0;"></div>
</body>
<script>(async () => {document.body.style.background = "#022B4F"; // 背景色改为深色const env = {serviceUrl: "https://vjmap.com/server/api/v1",accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M",exampleMapId: "sys_zp"};let svc = new vjmap.Service(env.serviceUrl, env.accessToken)// 打开地图let res = await svc.openMap({mapid: env.exampleMapId, // 地图ID,(请确保此ID已存在,可上传新图形新建ID)mapopenway: vjmap.MapOpenWay.GeomRender, // 以几何数据渲染方式打开style: vjmap.openMapDarkStyle() // div为深色背景颜色时,这里也传深色背景样式})if (res.error) {message.error(res.error)}// 获取地图的范围let mapExtent = vjmap.GeoBounds.fromString(res.bounds);// 建立坐标系let prj = new vjmap.GeoProjection(mapExtent);// 新建地图对象let map = new vjmap.Map({container: 'map', // container IDstyle: svc.rasterStyle(), // 栅格瓦片样式center: prj.toLngLat(mapExtent.center()), // 中心点zoom: 2,renderWorldCopies: false});// 地图关联服务对象和坐标系map.attach(svc, prj);// 使地图全部可见map.fitMapBounds();await map.onLoad(); // 等待地图加载完成let enableAiMcpChat = true;if (enableAiMcpChat) {if (typeof VjmapMcpSdk !== "object") {let svc = map.getService();const _url = svc.baseUrl() + "version";// @ts-ignorelet res = await svc._get(_url, {});let version = '';if (res && "data" in res) {let data = res["data"];version = data.version}// 版本变化了,需不用缓存重新下载下// vjchat.umd.js 中没有打包 vjmap, 需要把vjmap做为全局对象window.vjmap = vjmap;// 如果没有环境await vjmap.addScript([{src: "https://vjmap.com/server/_cloud/lib/vjmap-mcp-sdk.umd.js" + `?ver=${version}`}])await vjmap.addScript([{src: "https://vjmap.com/server/_cloud/lib/ai-chat-lib.umd.js" + `?ver=${version}`}])let apiUrl = map.getService().baseUrl();let token = map.getService().accessToken;// 初始化MCP连接,使用新的简化APIconsole.log('初始化MCP连接...')let mcpInstance = await VjmapMcpSdk.initializeMCP({apiBase: apiUrl,autoConnect: true,mapInstance: map})const sessionId = mcpInstance.state.sessionId// 注册地图相关工具await mcpInstance.registerMapTools()// 注册自定义工具示例await registerCustomTool(mcpInstance, VjmapMcpSdk.z)chatContainer = document.createElement('div')chatContainer.id = 'ai-chat-container'chatContainer.style.cssText = `position: absolute;top: 0;left: 0;width: 100%;height: 100%;pointer-events: none;z-index: 1000;`// 为聊天容器添加CSS规则,让其子元素可以接收事件const style = document.createElement('style')style.textContent = `#ai-chat-container > * {pointer-events: auto !important;}`if (!document.head.querySelector('style[data-ai-chat]')) {style.setAttribute('data-ai-chat', 'true')document.head.appendChild(style)}document.body.appendChild(chatContainer)let chatLib = AiChatLib.createAiChatLib({container: chatContainer,apiUrl: apiUrl,token: token,sessionId: sessionId,mcpAddresses: ['{apiUrl}/ai/mcp?sessionId={sessionId}&include_categories=query,map,draw,custom&token={token}'],systemPrompt: '',quickQuestions: ['介绍下当前图形绘制了什么','定位到当前图中的一个表格',"查找当前图中文本中有'图'的文字并在相应位置加上点标记"],maxHistoryCount: 10,window: {width: 500,height: 780,right: 3,theme: 'dark',draggable: true,title: '唯杰地图AI问答'},debug: true,autoFocus: true})}}})();// flashPos 函数实现const flashPos = (bounds) => {map.fitMapBounds(vjmap.GeoBounds.fromArray(bounds), { padding: 300 })return new Promise((resolve) => {const routePath = vjmap.GeoBounds.fromArray(bounds).toPointArray();routePath.push(routePath[0])let geoLineDatas = [];geoLineDatas.push({points: map.toLngLat(routePath),properties: {opacity: 1.0}})let polylines = new vjmap.Polyline({data: geoLineDatas,lineColor: 'yellow',lineWidth: 3,lineOpacity: ['get', 'opacity'],isHoverPointer: false,isHoverFeatureState: false});polylines.addTo(map);vjmap.createAnimation({from: 1,to: 10,duration: 1000,onUpdate: (e) => {const data = polylines.getData();if (data && data.features && data.features[0]) {data.features[0].properties.opacity = parseInt(e.toString()) % 2 ? 1.0 : 0;polylines.setData(data);}},onStop: () => {polylines.remove()resolve({})},onComplete: () => {polylines.remove()resolve({})}})})}// 注册自定义工具示例async function registerCustomTool(mcpInstance, z) {if (!mcpInstance) return// 使用 zod 定义工具输入 schema// https://github.com/colinhacks/zodconst inputSchema = z.object({bounds: z.array(z.number()).length(4).describe('边界坐标数组 [minX, minY, maxX, maxY]')})// 创建工具定义const tool = VjmapMcpSdk.createTool('map_flash_position','在地图上闪烁显示指定边界区域',inputSchema,'custom')// 创建工具处理函数const handler = async (args) => {try {const validatedArgs = inputSchema.parse(args)const { bounds } = validatedArgs// 调用 flashPos 函数await flashPos(bounds)return {content: [{type: 'text',text: `已在地图上闪烁显示边界区域: [${bounds.join(', ')}]`}]}} catch (error) {return {content: [{type: 'text',text: `工具执行失败: ${error instanceof Error ? error.message : String(error)}`}],isError: true}}}// 注册工具mcpInstance.registerTool(tool, handler, "custom")// 重要:注册工具到服务器,这样服务端才能获取到自定义工具try {const serviceManager = mcpInstance.getServiceManager()if (serviceManager) {await serviceManager.registerToolsToServer()console.log('自定义工具已成功注册到服务器')}} catch (error) {console.error('注册自定义工具到服务器失败:', error)}}</script></html>
后台设置
在后台数据目录的data/ai/aisvr_config.yaml
中进行配置