# Map MCP Protocol

Traditional CAD map applications are operated by humans. Users need to learn complex CAD operation commands and map management workflows, and the operation cost is relatively high. Wouldn't it be very convenient if AI could help us operate maps?

If all CAD map applications could be intelligently transformed so that AI can understand and operate map data, everyone would have a virtual "map intelligent assistant." We would only need to "chat" with the map intelligent assistant like chatting, express our map requirements in natural language, and the intelligent assistant would automatically operate the map application to fulfill our needs. This would greatly improve people's work efficiency and experience in map processing, CAD drawing analysis, spatial data management, and more!

VJMap has implemented the technology of AI replacing humans to operate CAD map applications based on the MCP protocol, and applied it to our VJMap engine, achieving intelligent transformation of the VJMap system. Using MCP protocol tools, they are naturally supported for AI recognition and control, including map browsing, CAD drawing queries, spatial data extraction, graphic drawing, and other operations. It also provides a frontend SDK that supports rapid AI integration for existing map businesses and intelligentization. It currently supports frontend frameworks such as Vue, React, and Angular.

Since it is implemented based on the standard MCP protocol, it has universality and broad applicability. It can control map applications through various types of MCP Hosts—for example, through AI dialog boxes on web pages to control map applications, or through IDE tools such as Trae and Cursor, or through intelligent agent platforms such as Dify and Coze, or even through mobile apps, WeChat mini-programs, and other methods to remotely control your map application. We can chat with AI and let AI help us operate various map applications to fulfill our map-related needs.

# Demo

chatmcp.gif

VJMap Cloud Management Platform (opens new window) https://vjmap.com/app/cloud/#/

image-20250803183709135

image-20250803180303071

Click the MCP address in the upper-left corner of the interface to view the MCP address for this session.

image-20250803175643751

You can copy the current session's MCP address to other MCP clients such as cursor, trae, or cherry studio to ask questions about the current map.

Example of invoking map-related operations via MCP address in cursor: mcpcursor.gif

# MCP Architecture Principles and Flow

The VJMap MCP system adopts a server-frontend separation architecture and implements bidirectional communication through the MCP (Model Context Protocol). The system supports mixed invocation of server-side tools and frontend tools to implement complex map operations and interactions.

# Overall Architecture

image-20250803182907607

# Backend MCP Tools

Tool Name Category Type Description Main Parameters
listmaps map Backend Get map list information, supports getting all maps or version information for specified maps mapid, version, mapIds, workspace, pagination, curPage, pageCount
createMap drawing Backend Create new map tool, supports two creation methods: 1. Create map via fileid and mapid; 2. Create via combine into new map API createType, mapid, fileid, uploadname, geom, sourceMapid, sourceVersion, sourceClipbounds, fourParameter, layers, workspace
deletemap delete Backend Delete map function, supports deleting specified version or all versions. Important: Delete operations are irreversible, use with caution! mapid, version, retainVersionMaxCount, workspace
getMapImage map Backend Get map image based on bounds and layer list, returns binary PNG format. Supports layer visibility, auto-calculates image aspect ratio mapid, version, bounds, pictureWidth, layerOn, layerOff, backgroundColor, transparent, styleName, workspace, returnBase64
fullSearch query Backend CAD drawing full-text search, supports searching text content, layers, block names, etc. query, map_ver, workspace, type, bounding_box, time_range, limit, offset, fields, facet
queryFeatures query Backend Query map entities, supports conditional query, rectangle bounds query, point query, and expression query mapid, version, queryType, sql, bounds, point, radius, expression, workspace
extractTable query Backend Automatically extract table data from CAD drawings. When querying drawing info, call this tool first to extract table data for analysis mapid, version, bounds, workspace
sqlDocHelper query Backend Get SQL writing documentation and table structure for querying map entities, provides reference for AI to generate SQL No parameters
createDwgDocHelper drawing Backend Get parameter description and entity type definition for creating drawings, provides reference for AI to generate graphic entity JSON arrays No parameters

# Frontend Tools

Tool Name Category Type Description Main Parameters
map_get_info map Frontend Get current map details including zoom, center, bearing, pitch, bounds, layer info, workspace name, thumbnail URL. Coordinates use CAD system. No parameters
map_open_or_switch map Frontend Open or switch map based on mapid and version. Updates map context to backend after opening. mapid, version, isKeepOldLayers, isVectorStyle, isSetCenter, isFitBounds, layeron, layeroff, backcolor
map_view_control map Frontend Map view control - zoom, pan, rotate, pitch, zoom to bounds. All coordinates use CAD (x,y) operation, zoom, center, bearing, pitch, bounds, duration
map_execute_code runcode Frontend Execute custom JavaScript for complex map operations (requires VJMap help documentation MCP) code, description
map_draw_geojson draw Frontend Draw features from GeoJSON, supports all standard GeoJSON geometry types. Style in properties geojsonData
map_delete_drawn_features draw Frontend Delete drawn features, supports deleting specified instance or clearing all drawn layers instanceId, clearAll
map_create_markers draw Frontend Create one or more markers from GeoJSON, style from properties, supports custom style, drag, popup geojsonData
map_delete_markers draw Frontend Delete markers on map, supports deleting specified marker or clearing all markerId, clearAll

Users can customize business-related MCP tools through the SDK.

# MCP URL Composition

The VJMap MCP system URL supports tool filtering. You can control available tool categories and specific tools through URL parameters.

# URL Format

/ai/mcp?sessionId={sessionId}&include_categories={categories}&exclude_categories={categories}&include_tools={tools}&exclude_tools={tools}&token={token}
1

Official default MCP URL:

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

If only backend MCP tools are needed without frontend interaction, sessionId can be left empty:

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

# Parameter Description

Parameter Name Type Required Description Example
sessionId string Yes Session ID for current session session_123456
token string Yes Access token for authentication eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
include_categories string No Included tool categories, comma-separated query,map,drawing
exclude_categories string No Excluded tool categories, comma-separated delete,utility
include_tools string No Included tool names, comma-separated listmaps,fullSearch
exclude_tools string No Excluded tool names, comma-separated deletemap

# Tool Categories

Category Name Category Value Description Included Tools
Map Management map Basic map CRUD listmaps, getMapImage, map_get_info, map_open_or_switch, map_view_control
Data Query query Data query and search fullSearch, queryFeatures, extractTable, sqlDocHelper
Graphic Processing drawing Graphic creation and processing createMap, createDwgDocHelper, map_draw_geojson, map_create_markers
Delete Tools delete Map deletion deletemap
Drawing Tools draw Frontend drawing map_draw_geojson, map_delete_drawn_features, map_create_markers, map_delete_markers
Code Execution runcode Code execution map_execute_code
Frontend Custom custom Frontend custom category

# Usage Examples

# Example 1: Include only query and map tools

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

Note: Only enables query tools and map tools.

# Example 2: Exclude delete tools

/ai/mcp?sessionId=session_123456&exclude_categories=delete&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
1

Note: Enables all tools except delete, ensuring users cannot perform delete operations.

# Example 3: Include only specific tools

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

Note: Only enables listmaps, fullSearch, map_get_info.

# Example 4: Exclude specific tools

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

Note: Excludes deletemap and map_execute_code which may have security risks.

# Example 5: Combined include and exclude

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

# Notes

  1. Priority: include_tools and exclude_tools have higher priority than include_categories and exclude_categories
  2. Conflicts: If a tool is in both include_tools and exclude_tools, exclude_tools takes precedence
  3. Empty values: If a parameter is empty or not provided, system uses default (enable all tools)
  4. Security: Use exclude_categories=delete or exclude_tools=deletemap in production to prevent accidental deletes
  5. URL encoding: Encode special characters in parameter values

Effect when adding MCP address in cursor for Q&A: 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

When frontend interaction is needed, copy the MCP address from the MCP tools in the AI dialog.

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

Note: sessionId is different for each session; generate the address based on the current session.

# MCP Frontend SDK and Custom Tool Example

<!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"; // dark background
        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)
        // Open map
        let res = await svc.openMap({
            mapid: env.exampleMapId, // map ID (ensure it exists or upload to create)
            mapopenway: vjmap.MapOpenWay.GeomRender, // geometry render
            style: vjmap.openMapDarkStyle() // dark style for dark container
        })
        if (res.error) {
            message.error(res.error)
        }
        // Map extent
        let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
        // Create projection
        let prj = new vjmap.GeoProjection(mapExtent);

        // Create map
        let map = new vjmap.Map({
            container: 'map', // container ID
            style: svc.rasterStyle(), // raster tile style
            center: prj.toLngLat(mapExtent.center()), // center
            zoom: 2,
            renderWorldCopies: false
        });
        // Attach service and projection
        map.attach(svc, prj);
        // Fit map bounds
        map.fitMapBounds();
        await map.onLoad(); // wait for map load



        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
                }
                // Bypass cache when version changes
                // vjchat.umd.js does not bundle vjmap; expose globally
                window.vjmap = vjmap;
                // Load scripts if not present
                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;

                // Initialize MCP connection
                console.log('Initializing MCP...')
                let mcpInstance = await VjmapMcpSdk.initializeMCP({
                    apiBase: apiUrl,
                    autoConnect: true,
                    mapInstance: map
                })

                const sessionId = mcpInstance.state.sessionId
                // Register map tools
                await mcpInstance.registerMapTools()

                // Register custom tool example
                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;
                        `

                // Allow chat container children to receive events
                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: [
                        'Describe what is drawn in the current map',
                        'Locate a table in the current map',
                        "Find text containing 'map' and add point markers at those positions"
                    ],
                    maxHistoryCount: 10,
                    window: {
                        width: 500,
                        height: 780,
                        right: 3,
                        theme: 'dark',
                        draggable: true,
                        title: 'VJMap AI Q&A'
                    },
                    debug: true,
                    autoFocus: true
                })
            }
        }
    })();

    // flashPos implementation
    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({})
                }
            })
        })
    }

    // Register custom tool example
    async function registerCustomTool(mcpInstance, z) {
        if (!mcpInstance) return

        // Define tool input schema with zod
        const inputSchema = z.object({
            bounds: z.array(z.number()).length(4).describe('Bounds array [minX, minY, maxX, maxY]')
        })

        // Create tool definition
        const tool = VjmapMcpSdk.createTool(
            'map_flash_position',
            'Flash the given bounds on the map',
            inputSchema,
            'custom'
        )

        // Tool handler
        const handler = async (args) => {
            try {
                const validatedArgs = inputSchema.parse(args)
                const { bounds } = validatedArgs

                await flashPos(bounds)

                return {
                    content: [{
                        type: 'text',
                        text: `Flashed bounds on map: [${bounds.join(', ')}]`
                    }]
                }
            } catch (error) {
                return {
                    content: [{
                        type: 'text',
                        text: `Tool failed: ${error instanceof Error ? error.message : String(error)}`
                    }],
                    isError: true
                }
            }
        }

        mcpInstance.registerTool(tool, handler, "custom")

        // Register tool on server so backend can see it
        try {
            const serviceManager = mcpInstance.getServiceManager()
            if (serviceManager) {
                await serviceManager.registerToolsToServer()
                console.log('Custom tool registered on server')
            }
        } catch (error) {
            console.error('Failed to register custom tool on server:', 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

In the above code, ai-chat-lib.umd.js is the AI chat UI library.

In the above code, vjmap-mcp-sdk.umd.js is the vjmap MCP SDK library. It is mainly used for server communication and provides interfaces for users to customize tools. This library can also be installed via npm. Usage is as follows:

# VJMap MCP SDK Installation

npm install vjmcpsdk
1

# VJMap MCP SDK Usage

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

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

let mcpInstance = await initializeMCP({
    apiBase: apiUrl,
    autoConnect: true,
    mapInstance: map
})

const sessionId = mcpInstance.state.sessionId
await mcpInstance.registerMapTools()
await registerCustomTool(mcpInstance, z)

// flashPos function implementation
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
    const inputSchema = z.object({
        bounds: z.array(z.number()).length(4).describe('Bounds array [minX, minY, maxX, maxY]')
    })
    const tool = createTool(
        'map_flash_position',
        'Flash the given bounds on the map',
        inputSchema,
        'custom'
    )
    const handler = async (args) => {
        try {
            const validatedArgs = inputSchema.parse(args)
            const { bounds } = validatedArgs
            await flashPos(bounds)
            return {
                content: [{ type: 'text', text: `Flashed bounds on map: [${bounds.join(', ')}]` }]
            }
        } catch (error) {
            return {
                content: [{ type: 'text', text: `Tool failed: ${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('Custom tool registered on server')
        }
    } catch (error) {
        console.error('Failed to register custom tool on server:', 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

# Backend Configuration

Configure in data/ai/aisvr_config.yaml in the backend data directory.

image-20250803190902021