使用说明
mcp-geospatial
An MCP server for geospatial work: coordinates, geocoding, routing, terrain, weather and radio links. 22 tools, no API key required for any of them.
Built for logistics, telecom, agriculture, insurance, real estate, utilities, emergency response, surveying and mapping — anywhere a question starts with where.
The package and command are mcp-geospatial. The server is not on PyPI under
either name: install it from this repository, as below. The PyPI package
named geospatial-mcp is an unrelated project; installing it will not give
you this server.
"Where can I live within a 30-minute drive of work?" route_isochrone
"Where's the nearest depot by road to each of today's jobs?" route_matrix
"How long to drive from the depot to the customer?" route_directions
"How steep is this section of the trail?" terrain_profile
"How far will a 1800 MHz cell reach over suburban ground?" rf_link_budget
"What's the area of this field in hectares?" geom_measure
"Convert this grid reference the survey crew gave me" coord_convert
Install
Nothing to clone by hand. Every client below runs the server with uvx,
straight from this repository. To install it into an environment of your own
instead, use pip install git+https://github.com/ystoneman/geospatial-mcp.
Claude Code
claude mcp add geospatial -- uvx --from git+https://github.com/ystoneman/geospatial-mcp mcp-geospatial
Claude Desktop — claude_desktop_config.json
{
"mcpServers": {
"geospatial": {
"command": "uvx",
"args": ["--from", "git+https://github.com/ystoneman/geospatial-mcp", "mcp-geospatial"]
}
}
}
OpenAI Codex CLI — ~/.codex/config.toml
[mcp_servers.geospatial]
command = "uvx"
args = ["--from", "git+https://github.com/ystoneman/geospatial-mcp", "mcp-geospatial"]
Cursor — .cursor/mcp.json
{
"mcpServers": {
"geospatial": {
"command": "uvx",
"args": ["--from", "git+https://github.com/ystoneman/geospatial-mcp", "mcp-geospatial"]
}
}
}
VS Code — .vscode/mcp.json (note: the key is servers, not mcpServers)
{
"servers": {
"geospatial": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "git+https://github.com/ystoneman/geospatial-mcp", "mcp-geospatial"]
}
}
}
Zed — settings.json (the key is context_servers)
{
"context_servers": {
"geospatial": {
"command": { "path": "uvx", "args": ["--from", "git+https://github.com/ystoneman/geospatial-mcp", "mcp-geospatial"] }
}
}
}
Docker / self-hosted HTTP
docker build -t mcp-geospatial https://github.com/ystoneman/geospatial-mcp.git
docker run -p 8000:8000 mcp-geospatial --transport http
The HTTP transport binds 127.0.0.1 unless PORT is set by the platform. Put
it behind a reverse proxy with authentication before exposing it publicly.
Tools
Every tool taking a location accepts any of these, interchangeably:
48.8584, 2.2945 decimal degrees (canonical, latitude first)
48°51'30"N 2°17'40"E degrees / minutes / seconds
31UDQ4825211954 USNG / MGRS grid reference
u09tunquc geohash
geohash:u09tunquc geohash, marked so it is never taken for a place name
891fb46741bffff H3 cell
8FW4V75V+9R6 Plus Code
Eiffel Tower, Paris a place name, geocoded for you
core — 20 tools, on by default
| Tool | What it answers | Offline |
|---|---|---|
coord_convert |
This position in every notation at once | ● |
coord_describe |
Is this a valid coordinate, and how precise? | ● |
coord_transform_crs |
Between EPSG systems, with an accuracy figure | ● |
geom_measure |
Distance, bearing, area, perimeter, centroid | ● |
geom_transform |
Buffer, simplify, hull, centroid, repair | ● |
geom_overlay |
Intersection, union, difference | ● |
geom_relate |
Point-in-polygon, overlap, nearest distance | ● |
place_geocode |
Address or place name → coordinates | ○ |
place_reverse |
Coordinates → address | ○ |
place_search |
Fuel, hospitals, chargers, warehouses nearby | ○ |
route_directions |
Driving, cycling, walking, HGV directions | ○ |
route_isochrone |
Everywhere reachable in N minutes | ○ |
route_matrix |
Travel times between many origins and destinations | ○ |
terrain_elevation |
Height above sea level | ○ |
terrain_profile |
Elevation along a path, ascent and descent | ○ |
env_weather |
Current conditions and forecast | ○ |
sun_moon |
Sunrise, twilight, golden hour, shadow length | ● |
time_at_location |
Time zone, local time, next DST change | ● |
data_convert |
GeoJSON ⇄ WKT ⇄ KML ⇄ GPX ⇄ CSV ⇄ polyline | ● |
geo_capabilities |
What is enabled, and how to enable more | ● |
rf — 2 tools, on by default
| Tool | What it answers | Offline |
|---|---|---|
rf_link_budget |
Path-loss budget and cell radius | ● |
rf_towers |
Known cell towers nearby | ○ |
analysis — 2 tools, opt-in (the stats extra)
| Tool | What it answers | Offline |
|---|---|---|
stats_cluster |
Where do these points concentrate? | ● |
coord_grid_cells |
Bin points into H3 or geohash cells | ● |
● runs with no network at all. ○ calls a free public API.
Link budgets are an everyday tool for mobile operators, wireless ISPs, utilities running SCADA telemetry and IoT fleets: how far a radio reaches for a given power, frequency and environment.
This is one server among many. If you are working out which to use, Sparkgeo's geo-mcp-servers is a curated, machine-checked index of the geospatial MCP field, kept current and marked for liveness — a better place to compare coverage than any claim this README could make about itself. docs/REFERENCES.md lists the servers, standards and research this project leans on or points at.
Selecting toolsets
The examples call the command directly. If it is not installed, prefix it
with uvx --from git+https://github.com/ystoneman/geospatial-mcp, as in the
client configs above. all also needs the stats extra:
uvx --from 'mcp-geospatial[stats] @ git+https://github.com/ystoneman/geospatial-mcp' mcp-geospatial --toolsets all.
mcp-geospatial --toolsets core # 20 tools, drops the RF set
mcp-geospatial --toolsets all # adds the analysis toolset (needs [stats])
GEO_TOOLSETS=offline mcp-geospatial # 13 tools that never touch the network
mcp-geospatial --list-tools # print the catalogue and exit
offline is for field use with no signal. It keeps only tools that make no
network call under any input, so it excludes geom_measure and the place
tools — those accept a place name, which means they may geocode.
| Toolset | Tools | Needs |
|---|---|---|
core |
20 | nothing |
rf |
2 | nothing |
analysis |
2 | the stats extra (see above) |
Configuration
No API key is needed for anything. Every keyed provider has a keyless default.
| Variable | Purpose |
|---|---|
GEO_TOOLSETS |
default, all, offline, or a comma-separated list |
GEO_CACHE_DIR |
Response cache location (default ~/.cache/mcp-geospatial) |
GEO_CACHE |
0 disables the disk cache |
GEO_USER_AGENT |
Override the User-Agent sent to providers |
GEO_TRACE_FILE |
Append one JSON line per tool call to this file (off by default; records arguments, so it holds locations) |
GEO_TRACE_RESULTS |
1 adds a short preview of each result to those lines: headline values, the first warning and note, and whether it was cached. Never geometry |
NOMINATIM_URL, PHOTON_URL, OVERPASS_URL, VALHALLA_URL, OPEN_METEO_BASE_URL |
Point at self-hosted instances |
OPENROUTESERVICE_API_KEY |
Optional routing upgrade over the keyless default |
OPENCELLID_API_KEY |
Optional; larger cell-tower database |
OPEN_METEO_API_KEY |
Required for commercial weather use (see below) |
Data sources and licensing
Results carry the attribution their licence requires in meta.attribution —
pass it through when you display them. Two obligations worth knowing before you
build on this: OpenStreetMap-derived data (geocoding, places, routing) is
ODbL 1.0 and requires attribution, and Open-Meteo's free tier is
non-commercial, so commercial deployments need OPEN_METEO_API_KEY or a
self-hosted instance.
The public instances are free and shared. This server caches aggressively and
rate-limits per host to stay inside their usage policies; for heavy use,
self-host and set the corresponding *_URL. See
Sources and credits for the complete list.
What it gets right that is easy to get wrong
- Distances and areas are geodesic. Karney's algorithm on the WGS84
ellipsoid, accurate to millimetres at any distance. Not
111.32 km/degree, which is 0.25% out at 60° latitude and worse further north. - Buffers are circles on the ground. Buffering in degrees produces an ellipse stretched by 1/cos(latitude) — 2× wrong at 60°, 5× at 78°. Every buffer here is projected to an azimuthal equidistant plane first.
- Grid references truncate, they never round. A reference names the square a position falls in, so dropping digits must move the point to the coarser square's corner. Rounding would hand back a neighbouring square — a kilometre away at four digits, and confidently wrong.
- DEM gaps stay gaps. A missing elevation is reported as
null, never substituted with zero. Substituting sea level flattens a ridge into an entirely plausible plain, and every statistic downstream inherits it. - Place names are never silently guessed. "Springfield" matches cities of
near-equal prominence 1,500 km apart, so a tool asked about it lists them and
asks, rather than quietly answering for Illinois. When a name is clear enough
to use,
meta.notessays exactly which place it was taken to mean, and names any others that share it. - A place name is not mistaken for a code. "Bergen", "Denver" and "Sydney"
are spelt entirely from the geohash alphabet, and decoding them lands in
Alaska, the Atlantic and Turkey. A bare token is decoded as a geohash at
once only when it is written like one, in lowercase with a digit among the
letters. Anything else that could be a geohash is geocoded first, with a
note in
meta.notesgiving the geohash reading, and is read as a geohash only if no place has the name. Writegeohash:gcpvjto skip the lookup. A 4-character geohash such assw19could be a postcode district, so it is decoded with a note saying so. - Errors are errors. Failures raise, so the MCP response is marked
isError. A tool that returns{"status": "error"}is reported to the model as a success, and it will proceed on invented data. - Empirical models report their limits. COST231-Hata outside 1500–2000 MHz
still returns a number;
meta.notestells you it is out of range. - CRS transforms admit their accuracy. Every transform names the operation PROJ used for that point and its published accuracy. When a better operation needs a grid file that is not installed, the response names the file; when no published transformation exists at all, it says the datum shift was ignored.
Development
git clone https://github.com/ystoneman/geospatial-mcp && cd geospatial-mcp
uv sync --all-extras
make check # lint, types, tests — exactly what CI runs
make evals # tool-selection eval suite
make evals-network # the same, plus the cases that call live APIs
See AGENTS.md for the layout and conventions, and CONTRIBUTING.md to add a tool.
Sources and credits
Almost none of the geospatial computation here is ours. This package is an integration layer: it chooses which established libraries, published algorithms and public data services to expose to a language model, wires them together, and tries hard to get the units, datums and error handling right. The interesting work was done by the projects below, and this section exists so you can see exactly which ones you are depending on.
To put a number on it — of the Python that ships when you install this package,
1.0% is ours and 99.0% is dependencies (6,912 lines against 654,791), inside
a 150 MB installation of which 109 MB is compiled native libraries and 9 MB is
the EPSG registry. Counted by installing the package with no extras into an
empty environment and measuring site-packages.
Runtime dependencies
Everything below is permissively licensed. Licences were read from each
package's own LICENSE file, not from its classifiers — the two disagree more
often than you would expect.
| Package | Version | Licence | What it provides here |
|---|---|---|---|
mcp |
2.0.0 | MIT | The Model Context Protocol SDK: tool registration, transports, schemas |
pydantic |
2.12.5 | MIT | Tool input and output schemas, response validation |
numpy |
2.4.2 | BSD-3-Clause and others | Terrain arrays, profile statistics, hillshade kernels |
shapely |
2.1.2 | BSD-3-Clause | All polygon geometry — buffer, union, hulls, predicates |
pyproj |
3.7.2 | MIT | CRS transforms and every geodesic distance, bearing and area |
pygeodesy |
26.8.18 | MIT | Rhumb lines, great-circle intersections, cross-track distance, USNG/MGRS grid references |
h3 |
4.5.0 | Apache-2.0 | Uber's hexagonal hierarchical spatial index |
geohashr |
1.6.0 | BSD-3-Clause | Geohash encoding and decoding |
pluscodes |
2022.1.3 | Apache-2.0 | Open Location Code (Plus Codes) |
mercantile |
1.2.1 | BSD-3-Clause | XYZ tile and quadkey arithmetic |
astral |
3.2 | Apache-2.0 | Sun and moon position, twilight, golden hour |
tzfpy |
1.3.3 | MIT | Offline timezone lookup from coordinates |
geojson-pydantic |
2.1.1 | MIT | Typed GeoJSON models |
polyline |
2.0.4 | MIT | Google encoded polyline format |
pyshp |
3.1.6 | MIT | Shapefile reading and writing without GDAL |
Optional extras: pillow (MIT-CMU),
sgp4 and
skyfield (MIT),
scikit-learn, esda and
libpysal (BSD-3-Clause),
fast-tsp (MIT),
staticmap (Apache-2.0),
rasterio and
rio-tiler (BSD),
pystac-client (Apache-2.0),
morecantile (MIT).
Native libraries bundled inside those wheels
| Library | Licence | Reached through |
|---|---|---|
| GEOS | LGPL-2.1-or-later | shapely — used unmodified and dynamically linked; see THIRD_PARTY_NOTICES.md for how the licence is satisfied |
| PROJ | MIT | pyproj, with the EPSG registry |
GEOS is the one copyleft component in the tree. It is unavoidable — it sits under Shapely, GeoPandas, OGR, PostGIS and DuckDB-spatial alike, and there is no permissive replacement with equivalent overlay correctness.
Live data services
None require an API key. Results carry the attribution their licence demands in
meta.attribution; pass it through when you display them.
| Service | Provides | Data licence |
|---|---|---|
| Nominatim / Photon | Geocoding, reverse geocoding | OpenStreetMap, ODbL 1.0 |
| Overpass API | Points of interest by category | OpenStreetMap, ODbL 1.0 |
| Valhalla via FOSSGIS | Routing, isochrones, matrices | OpenStreetMap, ODbL 1.0 |
| Open-Meteo | Weather, elevation, air quality | CC-BY 4.0 — free tier is non-commercial |
| Copernicus GLO-90 DEM | Elevation, via Open-Meteo | © DLR e.V. 2010–2014, © Airbus Defence and Space GmbH 2014–2018 |
| BeaconDB | Cell towers (keyless default) | Public domain (CC0) |
| OpenCelliD | Cell towers (optional, keyed) | CC-BY-SA 4.0 — share-alike on derived databases |
Algorithms, and where they come from
Every computation in this package traces to a library, a published formula or a public service. None of it was invented here, and the literature is cited so you can check the implementation against the source.
| Capability | Implemented by | Source |
|---|---|---|
| Geodesic distance, bearing, area | PROJ, via pyproj.Geod |
Karney, C. F. F. (2013), "Algorithms for geodesics", Journal of Geodesy 87(1), 43–55 |
| Datum and projection transforms | PROJ | The EPSG Geodetic Parameter Dataset |
| USNG / MGRS grid references | pygeodesy |
FGDC-STD-011-2001, the United States National Grid; the same squares are MGRS outside the US |
| Polygon overlay, buffering, hulls | GEOS, via shapely |
The OGC Simple Features specification |
| Line simplification | GEOS | Douglas, D. H. & Peucker, T. K. (1973), The Canadian Cartographer 10(2), 112–122 |
| Hexagonal spatial index | h3 |
Uber H3 |
| Geohash | geohashr |
Niemeyer, G. (2008) |
| Plus Codes | pluscodes |
Google Open Location Code specification |
| Free-space path loss | this package | ITU-R P.525, "Calculation of free-space attenuation" |
| Okumura-Hata path loss | this package | Hata, M. (1980), IEEE Trans. Veh. Technol. 29(3), 317–325; after Okumura et al. (1968) |
| COST 231-Hata path loss | this package | COST Action 231 final report (1999), ch. 4 |
| Two-ray ground reflection | this package | Standard textbook propagation model |
| Slope, aspect, hillshade | this package | Horn, B. K. P. (1981), "Hill shading and the reflectance map", Proc. IEEE 69(1), 14–47 |
| Terrarium DEM decode | this package | Mapzen Terrain Tiles published encoding |
| Sun and moon position | astral |
Standard astronomical almanac formulae |
| DBSCAN clustering | scikit-learn |
Ester, M. et al. (1996), KDD-96 |
| HDBSCAN clustering | scikit-learn |
Campello, R. J. G. B. et al. (2013), PAKDD |
"Implemented by: this package" means the formula was written out from the cited
publication in geo/rf.py or
geo/terrain.py, because no permissively
licensed Python library implements it. The mathematics is not ours; only the
transcription is.
What this package actually adds
Given the above, it is worth being plain about what is left:
- The composition — which capabilities belong in one server, and what the tool surface should look like for a model rather than a human.
- Correct units and datums at the seams. Geodesic buffering via an azimuthal equidistant reprojection, geodesic path sampling, geodesic area — the places where naive degree arithmetic silently gives wrong answers.
- A shared HTTP layer with per-host rate limiting, a disk cache, bounded retries and mirror failover, so the free public services are used within their stated policies.
- Error design — tools raise rather than returning an error payload, and every message names the input, the problem and a valid example.
- Attribution plumbing, so licence obligations travel with the data.
- Tests and evals — 293 tests and a 31-case tool-selection eval library.
Full licence texts and obligations are in THIRD_PARTY_NOTICES.md. If you spot a missing or incorrect attribution, please open an issue — that is a bug.
Licence
MIT — see LICENSE.