mapbox-create-map-mcp 1.3.1 → 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 +30 -1
- package/package.json +3 -3
- package/src/icon.js +129 -0
- package/src/server.js +147 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ npx mapbox-create-map-mcp
|
|
|
47
47
|
|
|
48
48
|
输入参数:
|
|
49
49
|
- `name` (string, 必需): 弹框样式名称
|
|
50
|
-
- `htmlContent` (string, 必需): 弹框的HTML
|
|
50
|
+
- `htmlContent` (string, 必需): 弹框的HTML内容,**支持`{fieldName}`占位符动态绑定数据**
|
|
51
51
|
- `theme` (string, 可选): 预设主题 `light` | `dark` | `minimal` | `custom`
|
|
52
52
|
- `closeButton` (boolean, 可选): 是否显示关闭按钮
|
|
53
53
|
- `closeOnClick` (boolean, 可选): 点击地图时是否关闭
|
|
@@ -58,6 +58,35 @@ npx mapbox-create-map-mcp
|
|
|
58
58
|
- `altitude` (number, 可选): 海拔高度(米)
|
|
59
59
|
- `className` (string, 可选): 自定义CSS类名
|
|
60
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
|
+
|
|
61
90
|
返回结果示例:
|
|
62
91
|
```json
|
|
63
92
|
{
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mapbox-create-map-mcp",
|
|
3
|
-
"version": "1.
|
|
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": "
|
|
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
|
@@ -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',
|
|
@@ -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
|
}
|