bbox-mcp-server 1.2.4 → 1.2.6
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 +320 -274
- package/dist/index.js +145 -35
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,274 +1,320 @@
|
|
|
1
|
-
# 🌍 bbox-mcp-server
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1
|
+
# 🌍 bbox-mcp-server
|
|
2
|
+
|
|
3
|
+
**Ask AI about anything, anywhere — and verify the answer on a map. For Free**
|
|
4
|
+
|
|
5
|
+
The geospatial toolkit for AI agents. **6 tools, zero config** — give any LLM the ability to find, query, convert, and aggregate spatial data using open data. No API keys or signups required to start.
|
|
6
|
+
|
|
7
|
+
Every response includes a shareable verification link to **[vibhorsingh.com/boundingbox](https://vibhorsingh.com/boundingbox/)** — click it to visually confirm results on an interactive map. No other MCP server does this.
|
|
8
|
+
|
|
9
|
+
[](https://nodejs.org/) [](./LICENSE)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What Can You Do With This?
|
|
15
|
+
|
|
16
|
+
**You don't have to be a GIS professional to make use of this.** Any AI agent with this MCP server can answer spatial questions using real OpenStreetMap data and not spit out hallucinated garbage.
|
|
17
|
+
|
|
18
|
+
| Ask your AI agent... | What happens under the hood |
|
|
19
|
+
|---|---|
|
|
20
|
+
| *"How many EV chargers are in downtown Denver?"* | Overpass query → H3 hex binning → density analysis |
|
|
21
|
+
| *"Is there a hospital near this Airbnb?"* | POI search with structured tags → map verification link |
|
|
22
|
+
| *"Find all playgrounds within 1km of this address*"* | Overpass query + radius filter → pinned results on a shareable map |
|
|
23
|
+
| *"Convert this WKT to GeoJSON in EPSG:3857"* | Format conversion across 6 inputs, 9 outputs, 3,900+ projections |
|
|
24
|
+
| *"Show me all bike-share stations in Amsterdam"* | Curated OSM tags → Overpass query → results on map |
|
|
25
|
+
| *"Compare park density across Seattle neighborhoods"* | Overpass + H3 aggregation → hex-binned spatial analysis |
|
|
26
|
+
|
|
27
|
+
Every answer comes with a link. Click it, see if the AI got it right and you can also share it with someone else.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What Makes This Different
|
|
32
|
+
|
|
33
|
+
Most AI tools give you text you have to trust. This one gives you an interactive map you can check and verify!
|
|
34
|
+
|
|
35
|
+
- **Verifiable** — Every response includes a public URL where you (or anyone) can visually confirm the results. This is the only MCP server that does this in any domain.
|
|
36
|
+
- **Deterministic** — Queries hit real OpenStreetMap data and return real coordinates. The AI interprets your question. The data comes from all the hard work done by all the awesome OSM volunteers.
|
|
37
|
+
- **Free** — No paid services. Overpass API is free, OSM data is free, the tool is free. A Mapbox token (free tier, no payment info) unlocks natural language location search but isn't required.
|
|
38
|
+
- **Zero config** — `npx -y bbox-mcp-server` and you're running.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why This Exists
|
|
43
|
+
|
|
44
|
+
| The problem | How bbox-mcp solves it |
|
|
45
|
+
|---|---|
|
|
46
|
+
| *"I have WKT but the API needs a GeoJSON bbox in EPSG:3857."* | Parses 6 input formats, projects to 3,900+ EPSG codes, outputs in 9 formats — in one call. |
|
|
47
|
+
| *"I keep getting the wrong OSM tags for Overpass queries."* | `list_osm_tags` returns curated tag combos. No more hallucinated `amenity=grocery`. |
|
|
48
|
+
| *"How many hospitals are in this district?"* | `aggregate_overpass_h3` queries Overpass and bins results into H3 hexagons server-side. |
|
|
49
|
+
| *"Is this bounding box actually correct?"* | Every response includes a clickable map link for visual verification. |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Tools at a Glance
|
|
54
|
+
|
|
55
|
+
| Tool | What it does | Key params |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `get_bounds` | Convert and project a bbox across formats and coordinate systems | `bbox`, `epsg`, `format`, `coord_order`, `zoom` |
|
|
58
|
+
| `get_h3_indices` | Generate H3 hex cell indices covering a bbox | `bbox`, `resolution`, `compact` |
|
|
59
|
+
| `generate_share_url` | Create a shareable map link for a bbox | `bbox` |
|
|
60
|
+
| `search_overpass` | Query OpenStreetMap via Overpass QL within a bbox or radius | `bbox`, `query`, `limit`, `radius_meters` |
|
|
61
|
+
| `list_osm_tags` | Look up correct OSM tags for a category | `category` |
|
|
62
|
+
| `aggregate_overpass_h3` | Run an Overpass query and bin results into H3 hexagons | `bbox`, `query`, `resolution` |
|
|
63
|
+
|
|
64
|
+
All tools accept `location` (natural language, requires Mapbox token) or `bbox` (coordinates, WKT, GeoJSON, etc).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
Add to your MCP client config:
|
|
71
|
+
|
|
72
|
+
<details>
|
|
73
|
+
<summary><b>Claude Desktop</b> <i>(Click to expand)</i></summary>
|
|
74
|
+
|
|
75
|
+
Add to `claude_desktop_config.json`:
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"bbox": {
|
|
80
|
+
"command": "npx",
|
|
81
|
+
"args": ["-y", "bbox-mcp-server"]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
</details>
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary><b>Cursor</b> <i>(Click to expand)</i></summary>
|
|
90
|
+
|
|
91
|
+
Add to `.cursor/mcp.json`:
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"bbox": {
|
|
96
|
+
"command": "npx",
|
|
97
|
+
"args": ["-y", "bbox-mcp-server"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
</details>
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary><b>Windsurf</b> <i>(Click to expand)</i></summary>
|
|
106
|
+
|
|
107
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"bbox": {
|
|
112
|
+
"command": "npx",
|
|
113
|
+
"args": ["-y", "bbox-mcp-server"]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
<details>
|
|
121
|
+
<summary><b>VS Code (GitHub Copilot) or Google Antigravity</b> <i>(Click to expand)</i></summary>
|
|
122
|
+
|
|
123
|
+
Add to `.vscode/mcp.json` in your workspace:
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"servers": {
|
|
127
|
+
"bbox": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "bbox-mcp-server"]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
</details>
|
|
135
|
+
|
|
136
|
+
<br/>
|
|
137
|
+
|
|
138
|
+
> Try: *"Find all coffee shops within 500m of Times Square"*
|
|
139
|
+
|
|
140
|
+
### Optional Configuration
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"bbox": {
|
|
146
|
+
"command": "npx",
|
|
147
|
+
"args": ["-y", "bbox-mcp-server"],
|
|
148
|
+
"env": {
|
|
149
|
+
"MAPBOX_ACCESS_TOKEN": "pk.your-token-here",
|
|
150
|
+
"OVERPASS_API_URL": "https://your.custom.overpass.instance/api/interpreter"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| Variable | Default | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `MAPBOX_ACCESS_TOKEN` | — | Enables natural language location search (e.g. *"San Francisco"*) |
|
|
160
|
+
| `OVERPASS_API_URL` | auto | Custom Overpass endpoint. By default, rotates between `overpass-api.de` and `kumi.systems`. |
|
|
161
|
+
| `MAX_H3_CELLS` | `50000` | Safety cap for H3 grid generation |
|
|
162
|
+
|
|
163
|
+
*Or install globally: `npm install -g bbox-mcp-server`*
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Tool Reference
|
|
168
|
+
|
|
169
|
+
### `get_bounds`
|
|
170
|
+
|
|
171
|
+
Convert and project a bounding box across 6 input formats, 9 output formats, and 3,900+ coordinate systems. Returns the center point and tile coordinates for the centroid.
|
|
172
|
+
|
|
173
|
+
| Param | Type | Default | Description |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| `bbox` | string | — | Input geometry (coordinates, WKT, GeoJSON, ogrinfo extent) |
|
|
176
|
+
| `epsg` | string | `"4326"` | Target projection. Unknown codes auto-fetched from epsg.io. |
|
|
177
|
+
| `format` | string | `"csv"` | Output: `csv`, `wkt`, `geojson-bbox`, `geojson-polygon`, `leaflet`, `overpass`, `ogc-bbox`, `kml`, `stac-bbox` |
|
|
178
|
+
| `coord_order` | string | `"lng,lat"` | Swap to `"lat,lng"` for APIs that expect latitude first |
|
|
179
|
+
| `zoom` | number | `15` | Zoom level for tile coordinate calculation |
|
|
180
|
+
| `precision` | number | `6` | Decimal places in formatted output |
|
|
181
|
+
|
|
182
|
+
**💡 Prompt:** *"Get the bounding box for Central Park in WKT format projected to EPSG:32618"*
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### `get_h3_indices`
|
|
187
|
+
|
|
188
|
+
Generate Uber H3 hexagonal cell indices covering a bounding box.
|
|
189
|
+
|
|
190
|
+
| Param | Type | Default | Description |
|
|
191
|
+
|---|---|---|---|
|
|
192
|
+
| `bbox` | string | — | Input geometry |
|
|
193
|
+
| `resolution` | number | — | H3 resolution (0–15) |
|
|
194
|
+
| `compact` | boolean | `false` | Merge cells into coarser parents where possible |
|
|
195
|
+
| `return_geometry` | boolean | `false` | Include GeoJSON hex boundaries |
|
|
196
|
+
|
|
197
|
+
**💡 Prompt:** *"Give me H3 cells at resolution 7 for downtown Chicago, include the hex geometries"*
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### `search_overpass`
|
|
202
|
+
|
|
203
|
+
Execute an Overpass query within a bounding box or radius. Returns structured results with names, coordinates, and tags. The verification link plots each result as a pin on the map.
|
|
204
|
+
|
|
205
|
+
| Param | Type | Default | Description |
|
|
206
|
+
|---|---|---|---|
|
|
207
|
+
| `bbox` | string | — | Input geometry |
|
|
208
|
+
| `query` | string | — | Overpass QL (e.g. `nwr["amenity"="cafe"]`). The server wraps it in the appropriate spatial filter automatically. |
|
|
209
|
+
| `radius_meters` | number | — | Search radius in metres. Use only when the user specifies an explicit distance (e.g. `2000` for "within 2km"). Omit for area/region queries. |
|
|
210
|
+
| `limit` | number | `100` | Max elements returned |
|
|
211
|
+
|
|
212
|
+
**💡 Prompt:** *"Find all parking within 2km of JFK airport"*
|
|
213
|
+
|
|
214
|
+
**💡 Prompt:** *"Search for hospitals in Manhattan, limit 50"*
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### `list_osm_tags`
|
|
219
|
+
|
|
220
|
+
Look up the correct OpenStreetMap tags for a category before writing an Overpass query.
|
|
221
|
+
|
|
222
|
+
| Param | Type | Description |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `category` | string | Broad category (e.g. `"food"`, `"health"`, `"transport"`) |
|
|
225
|
+
|
|
226
|
+
**💡 Prompt:** *"What are the correct OSM tags for supermarkets?"*
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### `aggregate_overpass_h3`
|
|
231
|
+
|
|
232
|
+
Run an Overpass query and bin results into H3 hexagons for spatial density analysis. Returns counts per cell and GeoJSON hex boundaries.
|
|
233
|
+
|
|
234
|
+
| Param | Type | Default | Description |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| `bbox` | string | — | Input geometry |
|
|
237
|
+
| `query` | string | — | Overpass QL core query |
|
|
238
|
+
| `resolution` | number | `8` | H3 resolution for binning |
|
|
239
|
+
|
|
240
|
+
**💡 Prompt:** *"Aggregate all hospitals in Seattle into H3 bins at resolution 7"*
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### `generate_share_url`
|
|
245
|
+
|
|
246
|
+
Generate a shareable link to visualize a bounding box on the interactive map at vibhorsingh.com/boundingbox.
|
|
247
|
+
|
|
248
|
+
| Param | Type | Description |
|
|
249
|
+
|---|---|---|
|
|
250
|
+
| `bbox` | string | Input geometry |
|
|
251
|
+
|
|
252
|
+
**💡 Prompt:** *"Generate a share link for bbox 40.7128,-74.0060,40.7580,-73.9855"*
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Supported Input Formats
|
|
257
|
+
|
|
258
|
+
All tools auto-detect the input format. No need to specify which one you're using.
|
|
259
|
+
|
|
260
|
+
| Format | Example |
|
|
261
|
+
|---|---|
|
|
262
|
+
| Raw coordinates | `40.7128,-74.0060,40.7580,-73.9855` |
|
|
263
|
+
| WKT | `POLYGON((-74.006 40.712, -73.985 40.712, ...))` |
|
|
264
|
+
| GeoJSON | `{"type":"Feature","geometry":{...}}` |
|
|
265
|
+
| GeoJSON bbox | `{"bbox":[-74.006,40.712,-73.985,40.758]}` |
|
|
266
|
+
| ogrinfo extent | `Extent: (-74.006, 40.712) - (-73.985, 40.758)` |
|
|
267
|
+
| Space-separated | `40.7128 -74.0060 40.7580 -73.9855` |
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 🤖 For AI Agent Developers
|
|
272
|
+
|
|
273
|
+
### Response structure
|
|
274
|
+
|
|
275
|
+
Every tool returns two content blocks:
|
|
276
|
+
|
|
277
|
+
1. **Human-readable text** — formatted output with the map verification link
|
|
278
|
+
2. **Structured JSON** — all computed data, machine-parseable
|
|
279
|
+
|
|
280
|
+
Example `get_bounds` JSON response:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"original_wgs84": { "lat1": 40.7128, "lng1": -74.006, "lat2": 40.758, "lng2": -73.9855 },
|
|
285
|
+
"projected": { "xmin": -8238310.23, "ymin": 4970241.32, "xmax": -8235527.11, "ymax": 4976491.56 },
|
|
286
|
+
"center": { "lat": 40.7354, "lng": -73.99575 },
|
|
287
|
+
"tile_indices": { "z": 15, "x": 9660, "y": 12284 },
|
|
288
|
+
"epsg": "3857",
|
|
289
|
+
"coord_order": "lng,lat",
|
|
290
|
+
"area_km2": 8.681,
|
|
291
|
+
"dimensions": { "width_km": 1.714, "height_km": 5.066 },
|
|
292
|
+
"share_url": "https://vibhorsingh.com/boundingbox/#40.712800,-74.006000,40.758000,-73.985500"
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
### Error handling
|
|
298
|
+
|
|
299
|
+
All errors return `isError: true` with a descriptive message. Invalid coordinates, unknown EPSG codes, and oversized H3 requests all return clean errors — the server never crashes on bad input.
|
|
300
|
+
|
|
301
|
+
### Logging
|
|
302
|
+
|
|
303
|
+
Structured JSON logs go to **stderr** (stdout is reserved for MCP protocol). Each entry includes timestamp, level, and context.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
## Acknowledgments & Fair Use
|
|
307
|
+
|
|
308
|
+
### OpenStreetMap
|
|
309
|
+
|
|
310
|
+
This tool is built on [OpenStreetMap](https://www.openstreetmap.org/): A global dataset created and maintained by millions of volunteers. Every query you run returns data that someone walked, mapped, or verified by hand. If you find this useful, consider [contributing to OSM](https://wiki.openstreetmap.org/wiki/How_to_contribute).
|
|
311
|
+
|
|
312
|
+
### Responsible Usage
|
|
313
|
+
|
|
314
|
+
The public Overpass API instances are free community resources with limited capacity. Avoid tight loops, excessive polling, or bulk-scraping. For heavy workloads, set `OVERPASS_API_URL` to your own instance, see [Overpass API installation](https://wiki.openstreetmap.org/wiki/Overpass_API/Installation). The default server rotation helps spread load, but it's not a substitute for responsible usage.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
## License
|
|
318
|
+
MIT
|
|
319
|
+
|
|
320
|
+
[](https://lobehub.com/mcp/iamvibhorsingh-bbox-mcp-server)
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import proj4 from "proj4";
|
|
|
7
7
|
import proj4defs from "./proj4defs.json" with { type: "json" };
|
|
8
8
|
import { wktToGeoJSON as parseWKT } from "@terraformer/wkt";
|
|
9
9
|
const SERVER_NAME = "bbox-mcp-server";
|
|
10
|
-
const SERVER_VERSION = "1.2.
|
|
10
|
+
const SERVER_VERSION = "1.2.6";
|
|
11
11
|
const MAX_H3_CELLS = process.env.MAX_H3_CELLS ? parseInt(process.env.MAX_H3_CELLS, 10) : 50000;
|
|
12
12
|
const MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN;
|
|
13
13
|
const MAX_INPUT_LENGTH = 1_000_000; // 1 MB input size guard
|
|
@@ -322,6 +322,21 @@ function buildShareUrl(bbox) {
|
|
|
322
322
|
const hash = `${bbox.lat1.toFixed(6)},${bbox.lng1.toFixed(6)},${bbox.lat2.toFixed(6)},${bbox.lng2.toFixed(6)}`;
|
|
323
323
|
return `https://vibhorsingh.com/boundingbox/#${hash}`;
|
|
324
324
|
}
|
|
325
|
+
function buildShareUrlWithMarkers(bbox, elements) {
|
|
326
|
+
const bboxPart = `${bbox.lat1.toFixed(6)},${bbox.lng1.toFixed(6)},${bbox.lat2.toFixed(6)},${bbox.lng2.toFixed(6)}`;
|
|
327
|
+
const MAX_MARKERS = 30;
|
|
328
|
+
const markerParts = elements
|
|
329
|
+
.filter(e => e.coordinates?.lat !== undefined && e.coordinates?.lon !== undefined)
|
|
330
|
+
.slice(0, MAX_MARKERS)
|
|
331
|
+
.map(e => {
|
|
332
|
+
let name = e.name || 'Point';
|
|
333
|
+
if (name.length > 30)
|
|
334
|
+
name = name.slice(0, 27) + '...';
|
|
335
|
+
return `${e.coordinates.lat.toFixed(6)},${e.coordinates.lon.toFixed(6)},${encodeURIComponent(name)}`;
|
|
336
|
+
});
|
|
337
|
+
const hashParts = [bboxPart, ...markerParts];
|
|
338
|
+
return `https://vibhorsingh.com/boundingbox/#${hashParts.join('|')}`;
|
|
339
|
+
}
|
|
325
340
|
function getH3Cells(bbox, resolution, compact) {
|
|
326
341
|
if (resolution < 0 || resolution > 15 || !Number.isInteger(resolution)) {
|
|
327
342
|
throw new Error(`Invalid H3 resolution: ${resolution}. Must be an integer between 0 and 15.`);
|
|
@@ -434,6 +449,54 @@ async function geocodeLocation(query) {
|
|
|
434
449
|
clearTimeout(timeout);
|
|
435
450
|
}
|
|
436
451
|
}
|
|
452
|
+
async function geocodeLocationPoint(query) {
|
|
453
|
+
if (!MAPBOX_ACCESS_TOKEN) {
|
|
454
|
+
throw new Error('Mapbox API token not configured. Set MAPBOX_ACCESS_TOKEN to use location search. Provide explicit coordinates via bbox instead.');
|
|
455
|
+
}
|
|
456
|
+
const controller = new AbortController();
|
|
457
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
458
|
+
try {
|
|
459
|
+
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(query)}.json?access_token=${MAPBOX_ACCESS_TOKEN}&limit=1`;
|
|
460
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
461
|
+
if (!response.ok)
|
|
462
|
+
throw new Error(`Geocoding failed: ${response.status} ${response.statusText}`);
|
|
463
|
+
const data = await response.json();
|
|
464
|
+
if (!data?.features?.length)
|
|
465
|
+
throw new Error(`No results found for location: ${query}`);
|
|
466
|
+
const feature = data.features[0];
|
|
467
|
+
if (feature.center) {
|
|
468
|
+
return { lat: feature.center[1], lng: feature.center[0] };
|
|
469
|
+
}
|
|
470
|
+
else if (feature.bbox) {
|
|
471
|
+
return { lat: (feature.bbox[1] + feature.bbox[3]) / 2, lng: (feature.bbox[0] + feature.bbox[2]) / 2 };
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
throw new Error(`Unable to determine center point for location: ${query}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch (err) {
|
|
478
|
+
if (err.name === 'AbortError')
|
|
479
|
+
throw new Error(`Geocoding timed out after ${FETCH_TIMEOUT_MS / 1000}s for: ${query}`);
|
|
480
|
+
throw err;
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
clearTimeout(timeout);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function buildShareUrlCircleWithMarkers(center, radiusMeters, elements) {
|
|
487
|
+
const circlePart = `C:${center.lat.toFixed(6)},${center.lng.toFixed(6)},${radiusMeters.toFixed(2)}`;
|
|
488
|
+
const MAX_MARKERS = 30;
|
|
489
|
+
const markerParts = elements
|
|
490
|
+
.filter(e => e.coordinates?.lat !== undefined && e.coordinates?.lon !== undefined)
|
|
491
|
+
.slice(0, MAX_MARKERS)
|
|
492
|
+
.map(e => {
|
|
493
|
+
let name = e.name || 'Point';
|
|
494
|
+
if (name.length > 30)
|
|
495
|
+
name = name.slice(0, 27) + '...';
|
|
496
|
+
return `${e.coordinates.lat.toFixed(6)},${e.coordinates.lon.toFixed(6)},${encodeURIComponent(name)}`;
|
|
497
|
+
});
|
|
498
|
+
return `https://vibhorsingh.com/boundingbox/#${[circlePart, ...markerParts].join('|')}`;
|
|
499
|
+
}
|
|
437
500
|
// ---------------------------------------------------------------------------
|
|
438
501
|
// Tool Handlers
|
|
439
502
|
// ---------------------------------------------------------------------------
|
|
@@ -533,13 +596,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
533
596
|
},
|
|
534
597
|
{
|
|
535
598
|
name: "search_overpass",
|
|
536
|
-
description: "Execute an Overpass QL query to find POIs, roads, or other OSM features within a bounding box
|
|
599
|
+
description: "Execute an Overpass QL query to find POIs, roads, or other OSM features within a bounding box or radius. The server wraps your query in the appropriate spatial filter and returns structured JSON with a map link.\n\nSEARCH MODES:\n- Bounding box (default): searches a rectangular area. Use for region/area queries ('hospitals in Manhattan', 'parks in downtown Seattle').\n- Circle: searches within an exact radius. Use ONLY when the user specifies an explicit distance ('within 2km of JFK', '500 metres from the Eiffel Tower', '1 mile from Times Square'). Omit radius_meters for vague terms like 'near' or 'close to'.\n\nCOMMON TAG EXAMPLES:\n- Restaurants: `nwr[\"amenity\"=\"restaurant\"]`\n- Pizza: `nwr[\"amenity\"=\"fast_food\"][\"cuisine\"=\"pizza\"]`\n- Supermarkets: `nwr[\"shop\"=\"supermarket\"]`\n- Parks: `nwr[\"leisure\"=\"park\"]`\n- Hospitals: `nwr[\"amenity\"=\"hospital\"]`\n- Schools: `nwr[\"amenity\"=\"school\"]`\n- Parking: `nwr[\"amenity\"=\"parking\"]`\n- Highways/Roads: `way[\"highway\"]`",
|
|
537
600
|
inputSchema: {
|
|
538
601
|
type: "object",
|
|
539
602
|
properties: {
|
|
540
603
|
location: {
|
|
541
604
|
type: "string",
|
|
542
|
-
description: "A text location to search for (e.g. 'San Francisco'). Requires MAPBOX_ACCESS_TOKEN env var. Either 'location' or 'bbox' MUST be provided."
|
|
605
|
+
description: "A text location to search for (e.g. 'San Francisco', 'JFK Airport'). Requires MAPBOX_ACCESS_TOKEN env var. Either 'location' or 'bbox' MUST be provided."
|
|
543
606
|
},
|
|
544
607
|
bbox: {
|
|
545
608
|
type: "string",
|
|
@@ -547,11 +610,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
547
610
|
},
|
|
548
611
|
query: {
|
|
549
612
|
type: "string",
|
|
550
|
-
description: "The Overpass QL core query. Example: `node[\"amenity\"=\"cafe\"]` or `nwr[\"leisure\"=\"park\"]`. DO NOT include
|
|
613
|
+
description: "The Overpass QL core query. Example: `node[\"amenity\"=\"cafe\"]` or `nwr[\"leisure\"=\"park\"]`. DO NOT include spatial filters, bounding box, or output directives — the server handles those automatically."
|
|
614
|
+
},
|
|
615
|
+
radius_meters: {
|
|
616
|
+
type: "number",
|
|
617
|
+
description: "Search radius in metres. Enables circle mode — use ONLY when the user specifies an explicit distance (e.g. '2km' → 2000, '500 metres' → 500, '1 mile' → 1609). Omit entirely for area/region queries or vague terms like 'near'."
|
|
551
618
|
},
|
|
552
619
|
limit: {
|
|
553
620
|
type: "number",
|
|
554
|
-
description: "Maximum number of elements to return.
|
|
621
|
+
description: "Maximum number of elements to return. Default is 100. Increase if you need more results."
|
|
555
622
|
}
|
|
556
623
|
},
|
|
557
624
|
required: ["query"],
|
|
@@ -737,7 +804,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
737
804
|
content: [
|
|
738
805
|
{
|
|
739
806
|
type: "text",
|
|
740
|
-
text: `H3
|
|
807
|
+
text: `H3 cells generated: ${cells.length.toLocaleString()}\nResolution: ${resolution} | Compacted: ${compact}\n\nCells:\n${JSON.stringify(cells)}\n\nView on map: ${shareUrl}`
|
|
741
808
|
},
|
|
742
809
|
{
|
|
743
810
|
type: "text",
|
|
@@ -759,7 +826,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
759
826
|
content: [
|
|
760
827
|
{
|
|
761
828
|
type: "text",
|
|
762
|
-
text: `View on map: ${shareUrl}
|
|
829
|
+
text: `View on map: ${shareUrl}`
|
|
763
830
|
},
|
|
764
831
|
{
|
|
765
832
|
type: "text",
|
|
@@ -775,6 +842,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
775
842
|
const args = request.params.arguments || {};
|
|
776
843
|
const queryRaw = args.query;
|
|
777
844
|
const limit = args.limit || 100;
|
|
845
|
+
const radiusMeters = args.radius_meters;
|
|
846
|
+
const useCircle = typeof radiusMeters === 'number' && radiusMeters > 0;
|
|
778
847
|
if (!queryRaw) {
|
|
779
848
|
return { content: [{ type: "text", text: "Error: 'query' is required (e.g. `node[\"amenity\"=\"restaurant\"]`)." }], isError: true };
|
|
780
849
|
}
|
|
@@ -782,29 +851,65 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
782
851
|
return { content: [{ type: "text", text: "Error: Query is too long (maximum 2000 characters)." }], isError: true };
|
|
783
852
|
}
|
|
784
853
|
if (queryRaw.includes("[out:") || queryRaw.includes("[timeout:") || queryRaw.includes("[bbox:")) {
|
|
785
|
-
return { content: [{ type: "text", text: "Error: Query must not contain
|
|
854
|
+
return { content: [{ type: "text", text: "Error: Query must not contain [out:], [timeout:], or [bbox:] directives. The server handles these automatically." }], isError: true };
|
|
786
855
|
}
|
|
856
|
+
let overpassQuery;
|
|
857
|
+
let searchModeLabel;
|
|
858
|
+
let centerPoint;
|
|
787
859
|
let bboxObj;
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
860
|
+
if (useCircle) {
|
|
861
|
+
if (args.location) {
|
|
862
|
+
try {
|
|
863
|
+
centerPoint = await geocodeLocationPoint(args.location);
|
|
864
|
+
}
|
|
865
|
+
catch (err) {
|
|
866
|
+
if (args.bbox) {
|
|
867
|
+
log('warn', `Geocoding failed for radius search, using bbox center`, { error: err.message });
|
|
868
|
+
const fb = parseBBox(args.bbox);
|
|
869
|
+
centerPoint = { lat: (fb.lat1 + fb.lat2) / 2, lng: (fb.lng1 + fb.lng2) / 2 };
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
log('error', `Geocoding failed for radius search: ${args.location}`, { error: err.message });
|
|
873
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
874
|
+
}
|
|
796
875
|
}
|
|
797
|
-
bboxObj = parseBBox(args.bbox);
|
|
798
876
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
877
|
+
else if (args.bbox) {
|
|
878
|
+
const fb = parseBBox(args.bbox);
|
|
879
|
+
centerPoint = { lat: (fb.lat1 + fb.lat2) / 2, lng: (fb.lng1 + fb.lng2) / 2 };
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
return { content: [{ type: "text", text: "Error: Either 'location' or 'bbox' argument must be provided." }], isError: true };
|
|
883
|
+
}
|
|
884
|
+
overpassQuery = `[out:json][timeout:25];\n(${queryRaw}(around:${radiusMeters},${centerPoint.lat},${centerPoint.lng}););\nout center;`;
|
|
885
|
+
searchModeLabel = `circle — ${radiusMeters}m radius around (${centerPoint.lat.toFixed(5)}, ${centerPoint.lng.toFixed(5)})`;
|
|
802
886
|
}
|
|
803
887
|
else {
|
|
804
|
-
|
|
888
|
+
if (args.location) {
|
|
889
|
+
try {
|
|
890
|
+
bboxObj = await geocodeLocation(args.location);
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
if (args.bbox) {
|
|
894
|
+
log('warn', `Geocoding failed, falling back to bbox argument`, { error: err.message });
|
|
895
|
+
bboxObj = parseBBox(args.bbox);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
log('error', `Geocoding failed for overpass location: ${args.location}`, { error: err.message });
|
|
899
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
else if (args.bbox) {
|
|
904
|
+
bboxObj = parseBBox(args.bbox);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
return { content: [{ type: "text", text: "Error: Either 'location' or 'bbox' argument must be provided." }], isError: true };
|
|
908
|
+
}
|
|
909
|
+
overpassQuery = `[out:json][timeout:25][bbox:${bboxObj.lat1},${bboxObj.lng1},${bboxObj.lat2},${bboxObj.lng2}];\n(${queryRaw};);\nout center;`;
|
|
910
|
+
searchModeLabel = `bounding box`;
|
|
805
911
|
}
|
|
806
|
-
log('info', `search_overpass starting`, { query: queryRaw });
|
|
807
|
-
const overpassQuery = `[out:json][timeout:25][bbox:${bboxObj.lat1},${bboxObj.lng1},${bboxObj.lat2},${bboxObj.lng2}];\n(${queryRaw};);\nout center;`;
|
|
912
|
+
log('info', `search_overpass starting`, { query: queryRaw, mode: useCircle ? 'circle' : 'bbox', radius_meters: radiusMeters });
|
|
808
913
|
const endpoints = [
|
|
809
914
|
"https://overpass-api.de/api/interpreter",
|
|
810
915
|
"https://overpass.kumi.systems/api/interpreter"
|
|
@@ -814,7 +919,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
814
919
|
}
|
|
815
920
|
let data = null;
|
|
816
921
|
let lastError = null;
|
|
817
|
-
// Formatting payload precisely for Overpass
|
|
818
922
|
const params = new URLSearchParams();
|
|
819
923
|
params.append('data', overpassQuery);
|
|
820
924
|
for (const endpoint of endpoints) {
|
|
@@ -836,7 +940,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
836
940
|
throw new Error(`Overpass API returned status: ${response.status} ${response.statusText}`);
|
|
837
941
|
}
|
|
838
942
|
data = await response.json();
|
|
839
|
-
break;
|
|
943
|
+
break;
|
|
840
944
|
}
|
|
841
945
|
catch (err) {
|
|
842
946
|
lastError = err;
|
|
@@ -848,20 +952,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
848
952
|
}
|
|
849
953
|
if (!data) {
|
|
850
954
|
if (lastError?.name === 'AbortError') {
|
|
851
|
-
return { content: [{ type: "text", text: `Error: Overpass API request timed out on all endpoints after ${(FETCH_TIMEOUT_MS * 4) / 1000}s
|
|
955
|
+
return { content: [{ type: "text", text: `Error: Overpass API request timed out on all endpoints after ${(FETCH_TIMEOUT_MS * 4) / 1000}s. The query or radius may be too large.` }], isError: true };
|
|
852
956
|
}
|
|
853
957
|
throw new Error(`Overpass query failed on all endpoints: ${lastError?.message}`);
|
|
854
958
|
}
|
|
855
|
-
let rawElements = data.elements || [];
|
|
856
|
-
// Parse raw elements into structured output
|
|
857
959
|
const seenIds = new Set();
|
|
858
|
-
let parsedElements =
|
|
960
|
+
let parsedElements = (data.elements || []).reduce((acc, e) => {
|
|
859
961
|
if (seenIds.has(e.id))
|
|
860
962
|
return acc;
|
|
861
963
|
seenIds.add(e.id);
|
|
862
964
|
const lat = e.lat ?? e.center?.lat;
|
|
863
965
|
const lon = e.lon ?? e.center?.lon;
|
|
864
|
-
// Only keep elements that actually have coordinates and tags (or allow untagged if requested, but structured output is better)
|
|
865
966
|
if (lat !== undefined && lon !== undefined) {
|
|
866
967
|
acc.push({
|
|
867
968
|
id: e.id,
|
|
@@ -879,23 +980,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
879
980
|
parsedElements = parsedElements.slice(0, limit);
|
|
880
981
|
truncated = true;
|
|
881
982
|
}
|
|
882
|
-
const shareUrl =
|
|
883
|
-
|
|
983
|
+
const shareUrl = useCircle && centerPoint
|
|
984
|
+
? buildShareUrlCircleWithMarkers(centerPoint, radiusMeters, parsedElements)
|
|
985
|
+
: buildShareUrlWithMarkers(bboxObj, parsedElements);
|
|
986
|
+
log('info', `search_overpass completed`, { elements: elementCount, returned: parsedElements.length, truncated, mode: useCircle ? 'circle' : 'bbox' });
|
|
884
987
|
const responseData = {
|
|
988
|
+
search_mode: useCircle ? 'circle' : 'bbox',
|
|
989
|
+
...(useCircle && centerPoint
|
|
990
|
+
? { center: centerPoint, radius_meters: radiusMeters }
|
|
991
|
+
: { original_bbox: bboxObj }),
|
|
885
992
|
generator: data.generator,
|
|
886
993
|
osm3s: data.osm3s,
|
|
887
994
|
elements: parsedElements,
|
|
888
995
|
total_count: elementCount,
|
|
889
996
|
returned_count: parsedElements.length,
|
|
890
997
|
truncated,
|
|
891
|
-
original_bbox: bboxObj,
|
|
892
998
|
share_url: shareUrl
|
|
893
999
|
};
|
|
1000
|
+
const topResults = parsedElements.slice(0, 5)
|
|
1001
|
+
.map((e) => `- [${e.type}] ${e.name} (${e.coordinates.lat.toFixed(5)}, ${e.coordinates.lon.toFixed(5)})`)
|
|
1002
|
+
.join('\n');
|
|
1003
|
+
const moreNote = parsedElements.length > 5 ? `\n … and ${parsedElements.length - 5} more (see JSON)` : '';
|
|
894
1004
|
return {
|
|
895
1005
|
content: [
|
|
896
1006
|
{
|
|
897
1007
|
type: "text",
|
|
898
|
-
text: `
|
|
1008
|
+
text: `Overpass query executed within a ${searchModeLabel}.\nFound ${elementCount} element${elementCount !== 1 ? 's' : ''}${truncated ? ` (showing first ${limit})` : ''}.\n\nTop results:\n${topResults}${moreNote}\n\nView on map: ${shareUrl}`
|
|
899
1009
|
},
|
|
900
1010
|
{
|
|
901
1011
|
type: "text",
|
|
@@ -1064,7 +1174,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1064
1174
|
content: [
|
|
1065
1175
|
{
|
|
1066
1176
|
type: "text",
|
|
1067
|
-
text: `Aggregated ${totalGrouped}
|
|
1177
|
+
text: `Aggregated ${totalGrouped} element${totalGrouped !== 1 ? 's' : ''} into ${activeHexes.length} H3 hexagon${activeHexes.length !== 1 ? 's' : ''} at resolution ${resolution}.\n\nTop hexagons by density:\n${topHexes.map(([hex, count]) => `- ${hex}: ${count} element${count !== 1 ? 's' : ''}`).join('\n')}\n\nView on map: ${shareUrl}`
|
|
1068
1178
|
},
|
|
1069
1179
|
{
|
|
1070
1180
|
type: "text",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bbox-mcp-server",
|
|
3
3
|
"mcpName": "io.github.iamvibhorsingh/bbox-mcp-server",
|
|
4
|
-
"version": "1.2.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.2.6",
|
|
5
|
+
"description": "The geospatial toolkit for AI agents — query, convert, and verify spatial data with zero config. 6 tools for bounding boxes, coordinate projection, H3 indexing, OpenStreetMap queries, and shareable map verification links.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "Vibhor Singh",
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"model-context-protocol",
|
|
18
18
|
"bounding-box",
|
|
19
19
|
"bbox",
|
|
20
|
+
"location",
|
|
21
|
+
"maps",
|
|
22
|
+
"spatial",
|
|
23
|
+
"geocoding",
|
|
24
|
+
"ai-agents",
|
|
25
|
+
"verification",
|
|
20
26
|
"gis",
|
|
21
27
|
"geospatial",
|
|
22
28
|
"coordinates",
|