mapbox-create-map-mcp 1.3.1 → 1.4.1

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
@@ -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
  {
@@ -77,6 +106,47 @@ npx mapbox-create-map-mcp
77
106
  }
78
107
  ```
79
108
 
109
+ #### CreateIcon - 搜索图标
110
+
111
+ 从 iconfont 搜索图标并返回 `data:image/svg+xml;base64` 格式的图标数据,可直接用于 `<img>` 标签的 `src` 属性。
112
+
113
+ 输入参数:
114
+ - `keyword` (string, 必需): 搜索关键词,例如 "车辆"、"建筑"、"定位"
115
+ - `id` (number, 可选): 图标ID,传入后会优先查找该ID的图标,方便复用之前的图标
116
+ - `index` (number, 可选): 选择搜索结果中的第几个图标,从0开始,范围0-49,默认0
117
+ - `color` (string, 可选): 图标颜色,十六进制格式如 "#FF0000"。**不传则保持原始颜色**
118
+ - `size` (number, 可选): 图标大小,单位像素,默认48
119
+
120
+ 返回结果示例:
121
+ ```json
122
+ {
123
+ "success": true,
124
+ "data": "data:image/svg+xml;base64,PHN2ZyBjbGFzcz0i...",
125
+ "name": "建筑",
126
+ "id": 3841001
127
+ }
128
+ ```
129
+
130
+ 使用示例:
131
+ ```javascript
132
+ // 基本搜索
133
+ { keyword: "建筑" }
134
+
135
+ // 选择第3个结果
136
+ { keyword: "建筑", index: 2 }
137
+
138
+ // 使用之前的ID精确查找
139
+ { keyword: "建筑", id: 3841001 }
140
+
141
+ // 指定颜色和大小
142
+ { keyword: "建筑", color: "#FF5500", size: 64 }
143
+ ```
144
+
145
+ 返回的 `data` 可直接用于:
146
+ ```html
147
+ <img src="data:image/svg+xml;base64,PHN2Zy..." />
148
+ ```
149
+
80
150
  ## JavaScript 库使用
81
151
 
82
152
  ### 基本使用
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mapbox-create-map-mcp",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
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,162 @@
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
+ id: {
14
+ type: 'number',
15
+ required: false,
16
+ description: '图标ID,传入后会优先查找该ID的图标'
17
+ },
18
+ index: {
19
+ type: 'number',
20
+ required: false,
21
+ default: 0,
22
+ description: '选择搜索结果中的第几个图标,从0开始,范围0-49'
23
+ },
24
+ color: {
25
+ type: 'string',
26
+ required: false,
27
+ description: '图标颜色,十六进制格式,例如: "#FF0000"。不传则保持原始颜色'
28
+ },
29
+ size: {
30
+ type: 'number',
31
+ required: false,
32
+ default: 48,
33
+ description: '图标大小,单位为像素'
34
+ }
35
+ };
36
+ }
37
+
38
+ /**
39
+ * 搜索图标
40
+ * @param {Object} config - 搜索配置
41
+ * @param {string} config.keyword - 搜索关键词
42
+ * @param {number} [config.id] - 图标ID,优先查找
43
+ * @param {number} [config.index] - 选择第几个(0-49)
44
+ * @param {string} [config.color] - 图标颜色,不传保持原色
45
+ * @param {number} [config.size] - 图标大小
46
+ * @returns {Promise<Object>} 返回图标数据
47
+ */
48
+ async search(config) {
49
+ if (!config.keyword || typeof config.keyword !== 'string') {
50
+ throw new Error('keyword 是必填字段,且必须是字符串');
51
+ }
52
+
53
+ const https = require('https');
54
+ const querystring = require('querystring');
55
+
56
+ // 确定 index,限制在0-49范围
57
+ let index = config.index !== undefined ? Math.max(0, Math.min(49, Math.floor(config.index))) : 0;
58
+ // pageSize 取 index + 1,确保能获取到指定位置的图标
59
+ const pageSize = index + 1;
60
+
61
+ const postData = querystring.stringify({
62
+ q: config.keyword,
63
+ sortType: 'updated_at',
64
+ page: 1,
65
+ pageSize: pageSize,
66
+ fromCollection: -1,
67
+ complex: 1,
68
+ t: Date.now(),
69
+ ctoken: 'null'
70
+ });
71
+
72
+ const options = {
73
+ hostname: 'www.iconfont.cn',
74
+ port: 443,
75
+ path: '/api/icon/search.json',
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
79
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
80
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
81
+ 'Referer': 'https://www.iconfont.cn/search/index',
82
+ 'X-Requested-With': 'XMLHttpRequest',
83
+ 'Content-Length': Buffer.byteLength(postData)
84
+ }
85
+ };
86
+
87
+ return new Promise((resolve, reject) => {
88
+ const req = https.request(options, (res) => {
89
+ let data = '';
90
+ res.on('data', (chunk) => { data += chunk; });
91
+ res.on('end', () => {
92
+ try {
93
+ const result = JSON.parse(data);
94
+ if (result.code === 200 && result.data && result.data.icons && result.data.icons.length > 0) {
95
+ const icons = result.data.icons;
96
+ let icon = null;
97
+
98
+ // 优先按 id 查找
99
+ if (config.id !== undefined) {
100
+ icon = icons.find(i => i.id === config.id);
101
+ }
102
+
103
+ // 没找到则按 index 选择,越界默认第一个
104
+ if (!icon) {
105
+ const safeIndex = index < icons.length ? index : 0;
106
+ icon = icons[safeIndex];
107
+ }
108
+
109
+ let svgContent = icon.show_svg;
110
+
111
+ if (!svgContent) {
112
+ resolve({ success: false, data: null, message: '图标没有SVG内容' });
113
+ return;
114
+ }
115
+
116
+ // 应用大小
117
+ const size = config.size || 48;
118
+
119
+ // 只有传入 color 时才替换颜色,否则保持原始样式
120
+ if (config.color) {
121
+ svgContent = svgContent.replace(/fill="[^"]*"/g, `fill="${config.color}"`);
122
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; fill: ${config.color}; overflow: hidden;"`);
123
+ } else {
124
+ // 不修改颜色,只设置宽高
125
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; overflow: hidden;"`);
126
+ }
127
+
128
+ // 转换为data URI (base64编码)
129
+ const base64 = Buffer.from(svgContent).toString('base64');
130
+ const dataUri = `data:image/svg+xml;base64,${base64}`;
131
+
132
+ resolve({
133
+ success: true,
134
+ data: dataUri,
135
+ name: icon.name,
136
+ id: icon.id
137
+ });
138
+ } else {
139
+ resolve({ success: false, data: null, message: '未找到匹配的图标' });
140
+ }
141
+ } catch (e) {
142
+ reject(new Error(`解析响应失败: ${e.message}`));
143
+ }
144
+ });
145
+ });
146
+
147
+ req.on('error', (e) => {
148
+ reject(new Error(`请求失败: ${e.message}`));
149
+ });
150
+
151
+ req.setTimeout(10000, () => {
152
+ req.destroy();
153
+ reject(new Error('请求超时'));
154
+ });
155
+
156
+ req.write(postData);
157
+ req.end();
158
+ });
159
+ }
160
+ }
161
+
162
+ module.exports = { CreateIcon };
package/src/server.js CHANGED
@@ -187,10 +187,162 @@ 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
+ id: {
203
+ type: 'number',
204
+ required: false,
205
+ description: '图标ID,传入后会优先查找该ID的图标'
206
+ },
207
+ index: {
208
+ type: 'number',
209
+ required: false,
210
+ default: 0,
211
+ description: '选择搜索结果中的第几个图标,从0开始,范围0-49'
212
+ },
213
+ color: {
214
+ type: 'string',
215
+ required: false,
216
+ description: '图标颜色,十六进制格式,例如: "#FF0000"。不传则保持原始颜色'
217
+ },
218
+ size: {
219
+ type: 'number',
220
+ required: false,
221
+ default: 48,
222
+ description: '图标大小,单位为像素'
223
+ }
224
+ };
225
+ }
226
+
227
+ async search(config) {
228
+ if (!config.keyword || typeof config.keyword !== 'string') {
229
+ throw new Error('keyword 是必填字段,且必须是字符串');
230
+ }
231
+
232
+ const https = require('https');
233
+ const querystring = require('querystring');
234
+
235
+ // 确定 index,限制在0-49范围
236
+ let index = config.index !== undefined ? Math.max(0, Math.min(49, Math.floor(config.index))) : 0;
237
+ // pageSize 取 index + 1,确保能获取到指定位置的图标
238
+ const pageSize = index + 1;
239
+
240
+ const postData = querystring.stringify({
241
+ q: config.keyword,
242
+ sortType: 'updated_at',
243
+ page: 1,
244
+ pageSize: pageSize,
245
+ fromCollection: -1,
246
+ complex: 1,
247
+ t: Date.now(),
248
+ ctoken: 'null'
249
+ });
250
+
251
+ const options = {
252
+ hostname: 'www.iconfont.cn',
253
+ port: 443,
254
+ path: '/api/icon/search.json',
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
258
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
259
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
260
+ 'Referer': 'https://www.iconfont.cn/search/index',
261
+ 'X-Requested-With': 'XMLHttpRequest',
262
+ 'Content-Length': Buffer.byteLength(postData)
263
+ }
264
+ };
265
+
266
+ return new Promise((resolve, reject) => {
267
+ const req = https.request(options, (res) => {
268
+ let data = '';
269
+ res.on('data', (chunk) => { data += chunk; });
270
+ res.on('end', () => {
271
+ try {
272
+ const result = JSON.parse(data);
273
+ if (result.code === 200 && result.data && result.data.icons && result.data.icons.length > 0) {
274
+ const icons = result.data.icons;
275
+ let icon = null;
276
+
277
+ // 优先按 id 查找
278
+ if (config.id !== undefined) {
279
+ icon = icons.find(i => i.id === config.id);
280
+ }
281
+
282
+ // 没找到则按 index 选择,越界默认第一个
283
+ if (!icon) {
284
+ const safeIndex = index < icons.length ? index : 0;
285
+ icon = icons[safeIndex];
286
+ }
287
+
288
+ let svgContent = icon.show_svg;
289
+
290
+ if (!svgContent) {
291
+ resolve({ success: false, data: null, message: '图标没有SVG内容' });
292
+ return;
293
+ }
294
+
295
+ // 应用大小
296
+ const size = config.size || 48;
297
+
298
+ // 只有传入 color 时才替换颜色,否则保持原始样式
299
+ if (config.color) {
300
+ svgContent = svgContent.replace(/fill="[^"]*"/g, `fill="${config.color}"`);
301
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; fill: ${config.color}; overflow: hidden;"`);
302
+ } else {
303
+ // 不修改颜色,只设置宽高
304
+ svgContent = svgContent.replace(/style="[^"]*"/, `style="width: ${size}px; height: ${size}px; vertical-align: middle; overflow: hidden;"`);
305
+ }
306
+
307
+ // 转换为data URI
308
+ const base64 = Buffer.from(svgContent).toString('base64');
309
+ const dataUri = `data:image/svg+xml;base64,${base64}`;
310
+
311
+ resolve({
312
+ success: true,
313
+ data: dataUri,
314
+ name: icon.name,
315
+ id: icon.id
316
+ });
317
+ } else {
318
+ resolve({ success: false, data: null, message: '未找到匹配的图标' });
319
+ }
320
+ } catch (e) {
321
+ reject(new Error(`解析响应失败: ${e.message}`));
322
+ }
323
+ });
324
+ });
325
+
326
+ req.on('error', (e) => {
327
+ reject(new Error(`请求失败: ${e.message}`));
328
+ });
329
+
330
+ req.setTimeout(10000, () => {
331
+ req.destroy();
332
+ reject(new Error('请求超时'));
333
+ });
334
+
335
+ req.write(postData);
336
+ req.end();
337
+ });
338
+ }
339
+ }
340
+
190
341
  class McpServer {
191
342
  constructor() {
192
343
  this.createMap = new CreateMap();
193
344
  this.createPopup = new CreatePopup();
345
+ this.createIcon = new CreateIcon();
194
346
  this.serverInfo = {
195
347
  name: 'mapbox-create-map-mcp',
196
348
  version: '1.0.0',
@@ -440,6 +592,29 @@ class McpServer {
440
592
  },
441
593
  required: ['name', 'htmlContent']
442
594
  }
595
+ },
596
+ // CreateIcon 工具
597
+ {
598
+ name: 'CreateIcon',
599
+ description: '从iconfont搜索图标并返回data:image/svg+xml格式的图标数据,可直接用于img标签的src属性或Mapbox图层的图标配置。',
600
+ inputSchema: {
601
+ type: 'object',
602
+ properties: {
603
+ keyword: {
604
+ type: 'string',
605
+ description: '搜索关键词,例如: "车辆", "地图", "定位", "建筑" 等'
606
+ },
607
+ color: {
608
+ type: 'string',
609
+ description: '图标颜色,十六进制格式,例如: "#FF0000", "#333333"'
610
+ },
611
+ size: {
612
+ type: 'number',
613
+ description: '图标大小,单位为像素,默认24'
614
+ }
615
+ },
616
+ required: ['keyword']
617
+ }
443
618
  }
444
619
  ]
445
620
  };
@@ -486,6 +661,9 @@ class McpServer {
486
661
  case 'CreatePopup':
487
662
  result = await this.createPopup.create(args);
488
663
  break;
664
+ case 'CreateIcon':
665
+ result = await this.createIcon.search(args);
666
+ break;
489
667
  default:
490
668
  throw new Error(`Unknown tool: ${name}`);
491
669
  }