# 地图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 帮我们操作各类地图应用,实现我们的地图相关需求。

# 效果展示:

chatmcp.gif

唯杰地图云端管理平台 (opens new window) https://vjmap.com/app/cloud/#/

image-20250803183709135

image-20250803180303071

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

image-20250803175643751

可复制当前会话的MCP地址 到其他MCP客户端cursortraecherry studio中对当前地图进行提问

如在cursor中通过MCP地址调用地图相关操作 mcpcursor.gif

# MCP架构原理与流程

唯杰地图MCP系统采用服务端-前端分离架构,通过MCP(Model Context Protocol)协议实现双向通信。系统支持服务端工具和前端工具的混合调用,实现复杂的地图操作和交互功能。

# 整体架构

image-20250803182907607

# 后端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}
1

官方默认mcp地址为

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

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

https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M
1

# 参数说明

参数名 类型 必填 描述 示例值
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...
1

说明: 只启用查询类工具(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...
1

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

# 示例3:只包含特定工具

/ai/mcp?sessionId=session_123456&include_tools=listmaps,fullSearch,map_get_info&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
1

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

# 示例4:排除特定工具

/ai/mcp?sessionId=session_123456&exclude_tools=deletemap,map_execute_code&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
1

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

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

/ai/mcp?sessionId=session_123456&include_categories=query,map&exclude_tools=deletemap&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
1

说明: 只启用查询和地图类工具,但进一步排除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地址进行问答,效果如下 image-20250803191926787

{
  "mcpServers": {
    "vjmap": {
      "url": "https://vjmap.com/server/ai/mcp?include_categories=query,map&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MiwiVXNlcm5hbWUiOiJhZG1pbjEiLCJOaWNrTmFtZSI6ImFkbWluMSIsIkF1dGhvcml0eUlkIjoiYWRtaW4iLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjo0ODEzMjY3NjM3LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTY1OTY2NjYzN30.cDXCH2ElTzU2sQU36SNHWoTYTAc4wEkVIXmBAIzWh6M"
    }
  }
}
1
2
3
4
5
6
7

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

image-20250803192608314

{
  "mcpServers": {
    "vjmap": {
      "url": "https://vjmap.com/server/ai/mcp?sessionId=session-6323558e&include_categories=query,map,draw,custom&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MSwiVXNlcm5hbWUiOiJyb290MSIsIk5pY2tOYW1lIjoicm9vdDEiLCJBdXRob3JpdHlJZCI6InJvb3QiLCJCdWZmZXJUaW1lIjo4NjQwMCwiZXhwIjoxOTQyMzg5NTc0LCJpc3MiOiJ2am1hcCIsIm5iZiI6MTYyNzAyODU3NH0.l1pP9FXu6ARDaaa-6ma0lp7ftbIk2t6rgmSmTqXry10"
    }
  }
}
1
2
3
4
5
6
7

注:每次会话的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 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258

上述代码中ai-chat-lib.umd.js是ai 会话的ui库。

上述代码中vjmap-mcp-sdk.umd.js是vjmap mcp 的sdk库。主要是用来于服务端通信, 同时提供接口让用户自定义工具, 这库也可以用npm进行安装。用法如下

# 唯杰地图MCP SDK 安装

npm install vjmcpsdk
1

# 唯杰地图MCP SDK 用法

import vjmap from vjmap
import { initializeMCP, createTool, z } from vjmcpsdk

let apiUrl = map.getService().baseUrl();
let token = map.getService().accessToken;

// 初始化MCP连接,使用新的简化API
console.log('初始化MCP连接...')
let mcpInstance = await initializeMCP({
    apiBase: apiUrl,
    autoConnect: true,
    mapInstance: map
})

const sessionId = mcpInstance.state.sessionId
// 注册地图相关工具
await mcpInstance.registerMapTools()

// 注册自定义工具示例 (如果要自定义工具,则用下面的示例)
await registerCustomTool(mcpInstance, z)


 // 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
    const inputSchema = z.object({
        bounds: z.array(z.number()).length(4).describe('边界坐标数组 [minX, minY, maxX, maxY]')
    })

    // 创建工具定义
    const tool = 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)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

# 后台设置

在后台数据目录的data/ai/aisvr_config.yaml中进行配置

image-20250803190902021