mapbox-create-map-mcp 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ npx mapbox-create-map-mcp
33
33
 
34
34
  ### MCP 工具
35
35
 
36
- **CreateMap** - 根据配置生成地图图层配置信息
36
+ #### CreateMap - 生成地图图层配置
37
37
 
38
38
  输入参数:
39
39
  - `title` (string, 必需): 地图标题
@@ -43,6 +43,69 @@ npx mapbox-create-map-mcp
43
43
  - `center` (array, 可选): 中心点 [经度, 纬度]
44
44
  - `zoom` (number, 可选): 缩放级别
45
45
 
46
+ #### CreatePopup - 生成弹框样式配置
47
+
48
+ 输入参数:
49
+ - `name` (string, 必需): 弹框样式名称
50
+ - `htmlContent` (string, 必需): 弹框的HTML内容,**支持`{fieldName}`占位符动态绑定数据**
51
+ - `theme` (string, 可选): 预设主题 `light` | `dark` | `minimal` | `custom`
52
+ - `closeButton` (boolean, 可选): 是否显示关闭按钮
53
+ - `closeOnClick` (boolean, 可选): 点击地图时是否关闭
54
+ - `closeOnMove` (boolean, 可选): 地图移动时是否关闭
55
+ - `anchor` (string, 可选): 锚点位置 `top` | `bottom` | `left` | `right` 等
56
+ - `offset` (array, 可选): 偏移量 [x, y]
57
+ - `maxWidth` (number, 可选): 最大宽度(px)
58
+ - `altitude` (number, 可选): 海拔高度(米)
59
+ - `className` (string, 可选): 自定义CSS类名
60
+
61
+ **占位符用法:**
62
+
63
+ `htmlContent` 支持使用 `{fieldName}` 占位符动态绑定 GeoJSON 要素的 `properties` 属性。当点击地图上的要素时,占位符会被替换为实际值。
64
+
65
+ ```html
66
+ <!-- htmlContent 示例 -->
67
+ <div>
68
+ <h3>{name}</h3>
69
+ <p>地址: {address}</p>
70
+ <p>类型: {type}</p>
71
+ <p>数值: {value}</p>
72
+ </div>
73
+ ```
74
+
75
+ 当点击的要素 `properties` 为:
76
+ ```json
77
+ {"name": "北京站", "address": "北京市东城区", "type": "火车站", "value": 12345}
78
+ ```
79
+
80
+ 弹框显示:
81
+ ```html
82
+ <div>
83
+ <h3>北京站</h3>
84
+ <p>地址: 北京市东城区</p>
85
+ <p>类型: 火车站</p>
86
+ <p>数值: 12345</p>
87
+ </div>
88
+ ```
89
+
90
+ 返回结果示例:
91
+ ```json
92
+ {
93
+ "success": true,
94
+ "config": {
95
+ "name": "深色主题",
96
+ "closeButton": true,
97
+ "closeOnClick": true,
98
+ "closeOnMove": false,
99
+ "anchor": "top",
100
+ "offset": [0, 0],
101
+ "maxWidth": 240,
102
+ "className": "dark-popup",
103
+ "altitude": 0,
104
+ "htmlContent": "<div style=\"padding: 15px; background: #2d2d2d; color: #fff;\">...</div>"
105
+ }
106
+ }
107
+ ```
108
+
46
109
  ## JavaScript 库使用
47
110
 
48
111
  ### 基本使用
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mapbox-create-map-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A Mapbox-based MCP tool for creating geographic data visualizations",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "mapbox-create-map-mcp": "./bin/cli.js"
9
+ "mapbox-create-map-mcp": "bin/cli.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "repository": {
59
59
  "type": "git",
60
- "url": "https://github.com/gis-team/mapbox-create-map.git"
60
+ "url": "git+https://github.com/gis-team/mapbox-create-map.git"
61
61
  },
62
62
  "bugs": {
63
63
  "url": "https://github.com/gis-team/mapbox-create-map/issues"
package/src/icon.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * CreateIcon - 图标搜索工具
3
+ * 从iconfont搜索图标并返回data:image/svg+xml格式
4
+ */
5
+ class CreateIcon {
6
+ constructor() {
7
+ this.metadata = {
8
+ keyword: {
9
+ type: 'string',
10
+ required: true,
11
+ description: '搜索关键词,例如: "车辆", "地图", "定位" 等'
12
+ },
13
+ color: {
14
+ type: 'string',
15
+ required: false,
16
+ default: '#1890FF',
17
+ description: '图标颜色,十六进制格式,例如: "#FF0000"'
18
+ },
19
+ size: {
20
+ type: 'number',
21
+ required: false,
22
+ default: 48,
23
+ description: '图标大小,单位为像素'
24
+ }
25
+ };
26
+ }
27
+
28
+ /**
29
+ * 搜索图标
30
+ * @param {Object} config - 搜索配置
31
+ * @param {string} config.keyword - 搜索关键词
32
+ * @param {string} [config.color] - 图标颜色
33
+ * @param {number} [config.size] - 图标大小
34
+ * @returns {Promise<Object>} 返回图标数据
35
+ */
36
+ async search(config) {
37
+ if (!config.keyword || typeof config.keyword !== 'string') {
38
+ throw new Error('keyword 是必填字段,且必须是字符串');
39
+ }
40
+
41
+ const https = require('https');
42
+ const querystring = require('querystring');
43
+
44
+ const postData = querystring.stringify({
45
+ q: config.keyword,
46
+ sortType: 'updated_at',
47
+ page: 1,
48
+ pageSize: 10,
49
+ fromCollection: -1,
50
+ complex: 1,
51
+ t: Date.now(),
52
+ ctoken: 'null'
53
+ });
54
+
55
+ const options = {
56
+ hostname: 'www.iconfont.cn',
57
+ port: 443,
58
+ path: '/api/icon/search.json',
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
62
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
63
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
64
+ 'Referer': 'https://www.iconfont.cn/search/index',
65
+ 'X-Requested-With': 'XMLHttpRequest',
66
+ 'Content-Length': Buffer.byteLength(postData)
67
+ }
68
+ };
69
+
70
+ return new Promise((resolve, reject) => {
71
+ const req = https.request(options, (res) => {
72
+ let data = '';
73
+ res.on('data', (chunk) => { data += chunk; });
74
+ res.on('end', () => {
75
+ try {
76
+ const result = JSON.parse(data);
77
+ if (result.code === 200 && result.data && result.data.icons && result.data.icons.length > 0) {
78
+ const icon = result.data.icons[0];
79
+ let svgContent = icon.show_svg;
80
+
81
+ if (!svgContent) {
82
+ resolve({ success: false, data: null, message: '图标没有SVG内容' });
83
+ return;
84
+ }
85
+
86
+ // 应用颜色和大小
87
+ const color = config.color || '#1890FF';
88
+ const size = config.size || 48;
89
+
90
+ // 替换fill颜色
91
+ svgContent = svgContent.replace(/fill="[^"]*"/g, `fill="${color}"`);
92
+ // 设置宽高
93
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; fill: ${color}; overflow: hidden;"`);
94
+
95
+ // 转换为data URI (base64编码)
96
+ const base64 = Buffer.from(svgContent).toString('base64');
97
+ const dataUri = `data:image/svg+xml;base64,${base64}`;
98
+
99
+ resolve({
100
+ success: true,
101
+ data: dataUri,
102
+ name: icon.name,
103
+ id: icon.id
104
+ });
105
+ } else {
106
+ resolve({ success: false, data: null, message: '未找到匹配的图标' });
107
+ }
108
+ } catch (e) {
109
+ reject(new Error(`解析响应失败: ${e.message}`));
110
+ }
111
+ });
112
+ });
113
+
114
+ req.on('error', (e) => {
115
+ reject(new Error(`请求失败: ${e.message}`));
116
+ });
117
+
118
+ req.setTimeout(10000, () => {
119
+ req.destroy();
120
+ reject(new Error('请求超时'));
121
+ });
122
+
123
+ req.write(postData);
124
+ req.end();
125
+ });
126
+ }
127
+ }
128
+
129
+ module.exports = { CreateIcon };
package/src/server.js CHANGED
@@ -87,8 +87,8 @@ class CreatePopup {
87
87
  htmlContent: {
88
88
  type: 'string',
89
89
  required: true,
90
- default: '<div style="padding: 10px;"><h3>标题</h3><p>内容</p></div>',
91
- description: '弹框的HTML内容,支持完整的HTML标签和内联样式'
90
+ default: '<div style="padding: 10px;"><h3>{name}</h3><p>{description}</p></div>',
91
+ description: '弹框的HTML内容,支持{fieldName}占位符动态绑定GeoJSON要素的properties属性。例如: "<h3>{name}</h3><p>{address}</p>"'
92
92
  },
93
93
  theme: {
94
94
  type: 'string',
@@ -187,10 +187,131 @@ class CreatePopup {
187
187
  }
188
188
  }
189
189
 
190
+ /**
191
+ * CreateIcon - 图标搜索工具
192
+ * 从iconfont搜索图标并返回data:image/svg+xml格式
193
+ */
194
+ class CreateIcon {
195
+ constructor() {
196
+ this.metadata = {
197
+ keyword: {
198
+ type: 'string',
199
+ required: true,
200
+ description: '搜索关键词,例如: "车辆", "地图", "定位" 等'
201
+ },
202
+ color: {
203
+ type: 'string',
204
+ required: false,
205
+ default: '#1890FF',
206
+ description: '图标颜色,十六进制格式,例如: "#FF0000"'
207
+ },
208
+ size: {
209
+ type: 'number',
210
+ required: false,
211
+ default: 48,
212
+ description: '图标大小,单位为像素'
213
+ }
214
+ };
215
+ }
216
+
217
+ async search(config) {
218
+ if (!config.keyword || typeof config.keyword !== 'string') {
219
+ throw new Error('keyword 是必填字段,且必须是字符串');
220
+ }
221
+
222
+ const https = require('https');
223
+ const querystring = require('querystring');
224
+
225
+ const postData = querystring.stringify({
226
+ q: config.keyword,
227
+ sortType: 'updated_at',
228
+ page: 1,
229
+ pageSize: 10,
230
+ fromCollection: -1,
231
+ complex: 1,
232
+ t: Date.now(),
233
+ ctoken: 'null'
234
+ });
235
+
236
+ const options = {
237
+ hostname: 'www.iconfont.cn',
238
+ port: 443,
239
+ path: '/api/icon/search.json',
240
+ method: 'POST',
241
+ headers: {
242
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
243
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
244
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
245
+ 'Referer': 'https://www.iconfont.cn/search/index',
246
+ 'X-Requested-With': 'XMLHttpRequest',
247
+ 'Content-Length': Buffer.byteLength(postData)
248
+ }
249
+ };
250
+
251
+ return new Promise((resolve, reject) => {
252
+ const req = https.request(options, (res) => {
253
+ let data = '';
254
+ res.on('data', (chunk) => { data += chunk; });
255
+ res.on('end', () => {
256
+ try {
257
+ const result = JSON.parse(data);
258
+ if (result.code === 200 && result.data && result.data.icons && result.data.icons.length > 0) {
259
+ const icon = result.data.icons[0];
260
+ let svgContent = icon.show_svg;
261
+
262
+ if (!svgContent) {
263
+ resolve({ success: false, data: null, message: '图标没有SVG内容' });
264
+ return;
265
+ }
266
+
267
+ // 应用颜色和大小
268
+ const color = config.color || '#1890FF';
269
+ const size = config.size || 48;
270
+
271
+ // 替换fill颜色
272
+ svgContent = svgContent.replace(/fill="[^"]*"/g, `fill="${color}"`);
273
+ // 设置宽高
274
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; fill: ${color}; overflow: hidden;"`);
275
+
276
+ // 转换为data URI
277
+ const base64 = Buffer.from(svgContent).toString('base64');
278
+ const dataUri = `data:image/svg+xml;base64,${base64}`;
279
+
280
+ resolve({
281
+ success: true,
282
+ data: dataUri,
283
+ name: icon.name,
284
+ id: icon.id
285
+ });
286
+ } else {
287
+ resolve({ success: false, data: null, message: '未找到匹配的图标' });
288
+ }
289
+ } catch (e) {
290
+ reject(new Error(`解析响应失败: ${e.message}`));
291
+ }
292
+ });
293
+ });
294
+
295
+ req.on('error', (e) => {
296
+ reject(new Error(`请求失败: ${e.message}`));
297
+ });
298
+
299
+ req.setTimeout(10000, () => {
300
+ req.destroy();
301
+ reject(new Error('请求超时'));
302
+ });
303
+
304
+ req.write(postData);
305
+ req.end();
306
+ });
307
+ }
308
+ }
309
+
190
310
  class McpServer {
191
311
  constructor() {
192
312
  this.createMap = new CreateMap();
193
313
  this.createPopup = new CreatePopup();
314
+ this.createIcon = new CreateIcon();
194
315
  this.serverInfo = {
195
316
  name: 'mapbox-create-map-mcp',
196
317
  version: '1.0.0',
@@ -430,7 +551,7 @@ class McpServer {
430
551
  },
431
552
  htmlContent: {
432
553
  type: 'string',
433
- description: '弹框的HTML内容,支持完整的HTML标签和内联样式'
554
+ description: '弹框的HTML内容,支持完整的HTML标签和内联样式。支持使用{fieldName}占位符动态绑定GeoJSON要素的properties属性,点击要素时占位符会被替换为实际值。例如:"<h3>{name}</h3><p>地址: {address}</p><p>类型: {type}</p>",当点击的要素properties为{"name":"北京站","address":"北京市东城区","type":"火车站"}时,弹框会显示"<h3>北京站</h3><p>地址: 北京市东城区</p><p>类型: 火车站</p>"。常用占位符: {name}名称, {id}标识, {address}地址, {value}数值, {description}描述等。'
434
555
  },
435
556
  theme: {
436
557
  type: 'string',
@@ -440,6 +561,29 @@ class McpServer {
440
561
  },
441
562
  required: ['name', 'htmlContent']
442
563
  }
564
+ },
565
+ // CreateIcon 工具
566
+ {
567
+ name: 'CreateIcon',
568
+ description: '从iconfont搜索图标并返回data:image/svg+xml格式的图标数据,可直接用于img标签的src属性或Mapbox图层的图标配置。',
569
+ inputSchema: {
570
+ type: 'object',
571
+ properties: {
572
+ keyword: {
573
+ type: 'string',
574
+ description: '搜索关键词,例如: "车辆", "地图", "定位", "建筑" 等'
575
+ },
576
+ color: {
577
+ type: 'string',
578
+ description: '图标颜色,十六进制格式,例如: "#FF0000", "#333333"'
579
+ },
580
+ size: {
581
+ type: 'number',
582
+ description: '图标大小,单位为像素,默认24'
583
+ }
584
+ },
585
+ required: ['keyword']
586
+ }
443
587
  }
444
588
  ]
445
589
  };
@@ -486,6 +630,9 @@ class McpServer {
486
630
  case 'CreatePopup':
487
631
  result = await this.createPopup.create(args);
488
632
  break;
633
+ case 'CreateIcon':
634
+ result = await this.createIcon.search(args);
635
+ break;
489
636
  default:
490
637
  throw new Error(`Unknown tool: ${name}`);
491
638
  }