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 对话框来控制地图应用,也可以通过TaceCursor 等 IDE 工具,或者通过 DifyCoze 等智能体平台来操控,甚至可以通过手机App、微信小程序等方式远程遥控你的地图应用。我们可以与 AI 对话,让 AI 帮我们操作各类地图应用,实现我们的地图相关需求。

效果展示:

唯杰地图云端管理平台 vjmap.com/app/cloud/#...

点击界面左上角的MCP地址,可查看此次会话的MCP地址

可复制当前会话的MCP地址 到其他MCP客户端cursortraecherry 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格式

ini 复制代码
/ai/mcp?sessionId={sessionId}&include_categories={categories}&exclude_categories={categories}&include_tools={tools}&exclude_tools={tools}&token={token}

官方默认mcp地址为

ini 复制代码
https://vjmap.com/server/ai/mcp?sessionId={前端每次会话的sessionID}&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M

如果只需要后端MCP工具,无需与前端交互sessionId参数可不填写。如

ini 复制代码
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:只包含查询和地图工具

ini 复制代码
/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:排除删除工具

ini 复制代码
/ai/mcp?sessionId=session_123456&exclude_categories=delete&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

说明: 启用除删除类工具外的所有工具,确保用户无法执行删除操作

示例3:只包含特定工具

ini 复制代码
/ai/mcp?sessionId=session_123456&include_tools=listmaps,fullSearch,map_get_info&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

说明: 只启用三个特定工具:listmaps(获取地图列表)、fullSearch(全文搜索)、map_get_info(获取地图信息)

示例4:排除特定工具

ini 复制代码
/ai/mcp?sessionId=session_123456&exclude_tools=deletemap,map_execute_code&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

说明: 启用所有工具,但排除deletemap(删除地图)和map_execute_code(执行代码)这两个可能有安全风险的工具

示例5:组合使用包含和排除

ini 复制代码
/ai/mcp?sessionId=session_123456&include_categories=query,map&exclude_tools=deletemap&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

说明: 只启用查询和地图类工具,但进一步排除deletemap工具,提供更精细的控制

注意事项

  1. 优先级规则: include_toolsexclude_tools 的优先级高于 include_categoriesexclude_categories
  2. 参数冲突: 如果同一个工具既在 include_tools 中又在 exclude_tools 中,exclude_tools 优先
  3. 空值处理: 如果某个参数为空或未提供,系统会使用默认行为(启用所有工具)
  4. 安全性: 建议在生产环境中使用 exclude_categories=deleteexclude_tools=deletemap 来防止意外删除操作
  5. URL编码: 如果参数值包含特殊字符,请进行URL编码

cursor中加入MCP地址进行问答,效果如下

json 复制代码
{
  "mcpServers": {
    "vjmap": {
      "url": "https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M"
    }
  }
}

如果需要与前端交互时,可在AI对话框中MCP工具中复制MCP地址。

json 复制代码
{
  "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及以自定义工具示例

html 复制代码
<!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 ID
            style: 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-ignore
                let 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连接,使用新的简化API
                console.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/zod
        const 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中进行配置

相关推荐
xyphf_和派孔明6 分钟前
关于echarts的性能优化考虑
前端·性能优化·echarts
wow_DG14 分钟前
【Pytorch✨】LSTM01 入门
人工智能·pytorch·lstm
PyHaVolask19 分钟前
HTML 表单进阶:用户体验优化与实战应用
前端·javascript·html·用户体验
不会计算机的g_c__b24 分钟前
搜索引擎评估革命:用户行为模型如何颠覆传统指标?
人工智能·自然语言处理·机器翻译
A了LONE27 分钟前
cv弹窗,退款确认弹窗
java·服务器·前端
萑澈1 小时前
大语言模型提示词工程详尽实战指南
人工智能·语言模型·自然语言处理
AntBlack1 小时前
闲谈 :AI 生成视频哪家强 ,掘友们有没有推荐的工具?
前端·后端·aigc
Blossom.1181 小时前
基于深度学习的医学图像分析:使用DeepLabv3+实现医学图像分割
人工智能·python·深度学习·yolo·目标检测·机器学习·迁移学习
808&Heartbreak*1 小时前
CNN实战项目
人工智能·神经网络·cnn
大公产经晚间消息1 小时前
网易云音乐硬刚腾讯系!起诉SM娱乐滥用市场支配地位
大数据·人工智能·娱乐