geospatial-mcp 0.1.0__tar.gz

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.
@@ -0,0 +1,319 @@
1
+ Metadata-Version: 2.5
2
+ Name: geospatial-mcp
3
+ Version: 0.1.0
4
+ Summary: A lightweight spatial data topology verification and analysis service for AI agents based on FastMCP
5
+ Project-URL: Homepage, https://github.com/geospatial-mcp/geospatial-mcp
6
+ Project-URL: Documentation, https://github.com/geospatial-mcp/geospatial-mcp#readme
7
+ Project-URL: Repository, https://github.com/geospatial-mcp/geospatial-mcp.git
8
+ Project-URL: Issues, https://github.com/geospatial-mcp/geospatial-mcp/issues
9
+ Author-email: GeoSpatial MCP Team <dev@geospatial-mcp.org>
10
+ License: MIT
11
+ Keywords: ai-agent,fastmcp,geojson,geospatial,gis,mcp,pyproj,shapely,spatial-analysis,topology
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: GIS
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: fastmcp>=2.0.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: pyproj>=3.6.0
27
+ Requires-Dist: shapely>=2.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: matplotlib>=3.8.0; extra == 'dev'
30
+ Requires-Dist: pillow>=10.0.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # ๐ŸŒ Geospatial MCP Server
35
+
36
+ [![PyPI version](https://img.shields.io/pypi/v/geospatial-mcp.svg)](https://pypi.org/project/geospatial-mcp/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
38
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
39
+ [![MCP Standard](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/)
40
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://pytest.org)
41
+
42
+ A zero-dependency (no GDAL/C++ build hell) Model Context Protocol (MCP) server that equips AI Agents (Claude, Cursor, Antigravity) with deterministic spatial analysis, GeoJSON auto-repair, and accurate projected metric calculations.
43
+
44
+ <!-- Instant Visual Value: Topology Auto-Healing Comparison -->
45
+ ![Topology Auto-Repair Comparison](docs/assets/topology_repair_comparison.png)
46
+
47
+ <!-- Terminal interaction demo -->
48
+ ![Demo](docs/assets/demo.gif)
49
+
50
+ ---
51
+
52
+ ## โšก The Problem It Solves
53
+
54
+ Large Language Models (LLMs) are notoriously bad at spatial reasoning and Cartesian mathematics:
55
+ - **Geometry Hallucination**: Self-intersecting polygons (bow-tie / figure-8 errors) and unclosed coordinate rings crash downstream GIS pipelines and map renderers.
56
+ - **Scale Distortion**: Treating spherical WGS84 geographic coordinates (latitude/longitude degrees) as flat Euclidean planes results in catastrophic area and perimeter errors (often 30% to 500% off depending on latitude).
57
+ - **Topology Blindness**: Inability to deterministically resolve point-in-polygon containment, spatial intersection surfaces, separation distances, and metric buffering.
58
+
59
+ `geospatial-mcp` bridges this gap by providing a lightweight, high-performance, GEOS-backed C-extension engine without heavy GIS infrastructure overhead.
60
+
61
+ ---
62
+
63
+ ## ๐Ÿš€ Quickstart (30 Seconds)
64
+
65
+ ### Option 1: Run with UVX (Recommended, Zero Install)
66
+ No local repository clone or manual environment setup required:
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "geospatial": {
72
+ "command": "uvx",
73
+ "args": ["geospatial-mcp"]
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### Option 2: Local Installation & Development
80
+
81
+ 1. **Clone and create virtual environment**:
82
+ ```bash
83
+ git clone https://github.com/your-org/geospatial-mcp.git
84
+ cd geospatial-mcp
85
+
86
+ python -m venv .venv
87
+ # Windows (PowerShell):
88
+ .\.venv\Scripts\Activate.ps1
89
+ # Linux / macOS:
90
+ source .venv/bin/activate
91
+ ```
92
+
93
+ 2. **Install package in editable mode**:
94
+ ```bash
95
+ pip install -e .
96
+ pip install pytest
97
+ ```
98
+
99
+ 3. **Configure MCP Client (Claude Desktop / Cursor / Antigravity)**:
100
+ ```json
101
+ {
102
+ "mcpServers": {
103
+ "geospatial": {
104
+ "command": "python",
105
+ "args": ["-m", "geospatial_mcp.server"],
106
+ "cwd": "/path/to/geospatial-mcp"
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## ๐Ÿ› ๏ธ Tools Reference
115
+
116
+ All tools are exposed via standard MCP transports (stdio) and feature robust input tolerance: they natively accept standard GeoJSON strings, raw coordinate arrays, GeoJSON dictionaries, Feature / FeatureCollection objects, and Markdown-wrapped JSON (` ```json `) without crashing.
117
+
118
+ ### 1. `validate_and_repair_geometry`
119
+ Validates topological compliance against OGC Simple Features standards. If topological defects exist (e.g., self-intersecting bow-tie polygons or unclosed linear rings), it extracts the exact cause of failure and automatically heals the geometry into a compliant GeoJSON object with RFC 7946 normalized ring winding.
120
+
121
+ - **Parameters**:
122
+ - `geojson` (`str | dict | list`, required): GeoJSON geometry, Feature, FeatureCollection, or raw coordinate array.
123
+ - **Returns**:
124
+ - `is_valid` (`bool`): Whether the original geometry was topologically valid.
125
+ - `error_reason` (`str | null`): Exact topological defect description if invalid (e.g. `Self-intersection[1 1]` or `Points of LinearRing do not form a closed linestring`).
126
+ - `repaired_geojson` (`dict | null`): Compliant, self-healed GeoJSON geometry.
127
+
128
+ #### Example Call
129
+ ```json
130
+ {
131
+ "name": "validate_and_repair_geometry",
132
+ "arguments": {
133
+ "geojson": {
134
+ "type": "Polygon",
135
+ "coordinates": [[[0, 0], [2, 2], [2, 0], [0, 2], [0, 0]]]
136
+ }
137
+ }
138
+ }
139
+ ```
140
+ #### Response
141
+ ```json
142
+ {
143
+ "is_valid": false,
144
+ "error_reason": "Self-intersection[1 1]",
145
+ "repaired_geojson": {
146
+ "type": "MultiPolygon",
147
+ "coordinates": [
148
+ [[[0.0, 0.0], [1.0, 1.0], [0.0, 2.0], [0.0, 0.0]]],
149
+ [[[1.0, 1.0], [2.0, 0.0], [2.0, 2.0], [1.0, 1.0]]]
150
+ ]
151
+ }
152
+ }
153
+ ```
154
+
155
+ ---
156
+
157
+ ### 2. `calculate_accurate_metrics`
158
+ Eliminates spherical degree distortion by deriving the optimal local UTM projection zone (`EPSG:32601-32660` for Northern hemisphere or `EPSG:32701-32760` for Southern hemisphere) from the polygon centroid, projecting to a planar Cartesian coordinate system via PyProj, and calculating distortion-free surface area and perimeter.
159
+
160
+ - **Parameters**:
161
+ - `geojson` (`str | dict | list`, required): Polygon or MultiPolygon GeoJSON.
162
+ - `unit` / `target_unit` (`str`, optional, default `"sq_meters"`): Desired unit (`"sq_meters"`, `"sq_kilometers"`, or `"hectares"`). Fully backwards-compatible with dual alias parsing.
163
+ - **Returns**:
164
+ - `area` (`float`): Planar area in the requested unit.
165
+ - `perimeter_meters` (`float`): Total boundary length in meters.
166
+ - `unit` (`str`): Target unit used.
167
+ - `projected_epsg` (`int`): EPSG integer code of the metric UTM projection applied (e.g. `32651`).
168
+
169
+ #### Example Call
170
+ ```json
171
+ {
172
+ "name": "calculate_accurate_metrics",
173
+ "arguments": {
174
+ "geojson": {
175
+ "type": "Polygon",
176
+ "coordinates": [[[121.47, 31.23], [121.48, 31.23], [121.48, 31.24], [121.47, 31.24], [121.47, 31.23]]]
177
+ },
178
+ "unit": "hectares"
179
+ }
180
+ }
181
+ ```
182
+ #### Response
183
+ ```json
184
+ {
185
+ "area": 105.74,
186
+ "perimeter_meters": 4118.62,
187
+ "unit": "hectares",
188
+ "projected_epsg": 32651
189
+ }
190
+ ```
191
+
192
+ ---
193
+
194
+ ### 3. `check_spatial_relationship`
195
+ Evaluates topological relationship predicates between two geometries (`contains`, `within`, `intersects`, `disjoint`). Automatically returns the shared intersection area in $m^2$ if overlapping, or the minimum metric separation distance in meters if disjoint.
196
+
197
+ - **Parameters**:
198
+ - `geom_a` (`str | dict | list`, required): First spatial object.
199
+ - `geom_b` (`str | dict | list`, required): Second spatial object.
200
+ - `predicate` (`str`, optional, default `"intersects"`): Predicate to evaluate (`"contains"`, `"within"`, `"intersects"`, `"disjoint"`).
201
+ - **Returns**:
202
+ - `result` (`bool`): Boolean verdict for the requested predicate.
203
+ - `details` (`dict`): Quantitative measurements including `intersection_area_sq_meters`, `distance_meters`, and `projected_epsg`.
204
+
205
+ #### Example Call
206
+ ```json
207
+ {
208
+ "name": "check_spatial_relationship",
209
+ "arguments": {
210
+ "geom_a": {"type": "Polygon", "coordinates": [[[0, 0], [2, 0], [2, 2], [0, 2], [0, 0]]]},
211
+ "geom_b": {"type": "Polygon", "coordinates": [[[1, 1], [3, 1], [3, 3], [1, 3], [1, 1]]]},
212
+ "predicate": "intersects"
213
+ }
214
+ }
215
+ ```
216
+ #### Response
217
+ ```json
218
+ {
219
+ "result": true,
220
+ "details": {
221
+ "predicate": "intersects",
222
+ "projected_epsg": 32631,
223
+ "intersection_area_sq_meters": 12285149372.45,
224
+ "distance_meters": 0.0
225
+ }
226
+ }
227
+ ```
228
+
229
+ ---
230
+
231
+ ### 4. `generate_buffer`
232
+ Constructs an accurate metric buffer (expansion or erosion) in the local UTM coordinate system, auto-heals any resulting self-intersections, and projects back to standard EPSG:4326 WGS84 GeoJSON.
233
+
234
+ - **Parameters**:
235
+ - `geojson` (`str | dict | list`, required): Input geometry.
236
+ - `distance_meters` (`float`, required): Buffer radius in meters (positive expands, negative erodes).
237
+ - `quad_segs` (`int`, optional, default `8`): Quadrant segments for circular arc approximation.
238
+ - **Returns**:
239
+ - `buffered_geojson` (`dict | null`): Normalized RFC 7946 GeoJSON dictionary in EPSG:4326.
240
+
241
+ #### Example Call
242
+ ```json
243
+ {
244
+ "name": "generate_buffer",
245
+ "arguments": {
246
+ "geojson": {"type": "Point", "coordinates": [121.5, 31.2]},
247
+ "distance_meters": 500.0,
248
+ "quad_segs": 16
249
+ }
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## ๐Ÿ—๏ธ Architecture & Project Structure
256
+
257
+ `geospatial-mcp` is designed to be completely decoupled from bulky C/C++ GIS frameworks:
258
+
259
+ ```
260
+ geospatial-mcp/
261
+ โ”œโ”€โ”€ pyproject.toml # Hatchling packaging and dependency constraints
262
+ โ”œโ”€โ”€ README.md # Complete architectural guide and tool references
263
+ โ”œโ”€โ”€ src/
264
+ โ”‚ โ””โ”€โ”€ geospatial_mcp/
265
+ โ”‚ โ”œโ”€โ”€ __init__.py # Package root
266
+ โ”‚ โ”œโ”€โ”€ server.py # FastMCP service instance & tool entrypoints
267
+ โ”‚ โ”œโ”€โ”€ core/ # Pure C-extension geometry engine
268
+ โ”‚ โ”‚ โ”œโ”€โ”€ __init__.py
269
+ โ”‚ โ”‚ โ”œโ”€โ”€ projection.py # Dynamic UTM zone deduction & CRS reprojector
270
+ โ”‚ โ”‚ โ””โ”€โ”€ geometry.py # Topology validation, metric calculations, relations & buffer
271
+ โ”‚ โ””โ”€โ”€ models.py # Pydantic v2 schemas (unit / target_unit alias support)
272
+ โ””โ”€โ”€ tests/
273
+ โ”œโ”€โ”€ __init__.py
274
+ โ”œโ”€โ”€ test_geometry.py # Core geometry, projection, bowtie repair, equator tests
275
+ โ””โ”€โ”€ test_server.py # FastMCP tool invocation, input tolerance, & error tests
276
+ ```
277
+
278
+ ### Why Zero GDAL?
279
+ Traditional GIS tools bundle GDAL, PROJ C libraries, and Python wrappers (Fiona, GeoPandas) that create:
280
+ - Massive container image sizes (> 1.5 GB).
281
+ - Platform-dependent C++ build failures and DLL hell on Windows / macOS.
282
+ - Potential process segmentation faults during concurrent execution.
283
+
284
+ `geospatial-mcp` uses pure **`shapely>=2.0`** (which packages standalone GEOS C libraries as pre-compiled wheels) and **`pyproj`**. It installs in seconds via `pip` or `uvx` with zero compilation steps.
285
+
286
+ ---
287
+
288
+ ## ๐Ÿ›ก๏ธ Fault Tolerance & Defensive Engineering
289
+
290
+ 1. **Unclosed Linear Rings**: In RFC 7946, polygon exterior and interior rings must be closed (first coordinate identical to last coordinate). If an unclosed ring is passed, the engine flags it (`is_valid=False`), describes the error, and automatically closes the ring during repair.
291
+ 2. **Malformed JSON / Syntax Errors**: Broken JSON strings, unclosed brackets, or invalid geometry types return structured, friendly JSON error responses with diagnostic hints rather than crashing the MCP server.
292
+ 3. **Hemisphere & Longitude Aware**: Automatically maps any coordinate on Earth into its corresponding UTM Zone (1 through 60) and applies EPSG:326xx for $lat \ge 0^\circ$ (North) and EPSG:327xx for $lat < 0^\circ$ (South), with seamless handling for geometries crossing the equator.
293
+ 4. **Dual Access Output Models**: All output objects implement `BaseResultModel`, permitting both object attribute access (`res.area`) and dictionary key access (`res['area']`).
294
+
295
+ ---
296
+
297
+ ## ๐Ÿงช Testing & Verification
298
+
299
+ The suite includes 20 comprehensive unit tests covering critical GIS boundary scenarios:
300
+
301
+ ```bash
302
+ # Run tests with pytest
303
+ pytest -v
304
+ ```
305
+
306
+ ### Verified Scenarios
307
+ - [x] **Bow-tie Self-Intersection Repair**: Resolves figure-8 polygons into valid OGC MultiPolygons.
308
+ - [x] **Unclosed Ring Auto-Healing**: Detects unclosed coordinate arrays and normalizes them.
309
+ - [x] **Equator Crossing & Multi-Longitude UTM**: Precision reprojection across latitude $0^\circ$ and longitudinal zones (Shanghai UTM 51N, Rio de Janeiro UTM 23S).
310
+ - [x] **Unit Conversions**: Metric validation across square meters, square kilometers, and hectares.
311
+ - [x] **Spatial Predicates**: Accurate metric distance on separation, intersection surface area on overlap.
312
+ - [x] **Parameter Alias Compatibility**: Identical resolution whether calling with `unit` or `target_unit`.
313
+ - [x] **Stdio Protocol Handshake**: Seamless MCP `initialize` JSON-RPC handshake verification.
314
+
315
+ ---
316
+
317
+ ## ๐Ÿ“„ License
318
+
319
+ MIT License. Free for open-source and commercial use.
@@ -0,0 +1,286 @@
1
+ # ๐ŸŒ Geospatial MCP Server
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/geospatial-mcp.svg)](https://pypi.org/project/geospatial-mcp/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/)
6
+ [![MCP Standard](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/)
7
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://pytest.org)
8
+
9
+ A zero-dependency (no GDAL/C++ build hell) Model Context Protocol (MCP) server that equips AI Agents (Claude, Cursor, Antigravity) with deterministic spatial analysis, GeoJSON auto-repair, and accurate projected metric calculations.
10
+
11
+ <!-- Instant Visual Value: Topology Auto-Healing Comparison -->
12
+ ![Topology Auto-Repair Comparison](docs/assets/topology_repair_comparison.png)
13
+
14
+ <!-- Terminal interaction demo -->
15
+ ![Demo](docs/assets/demo.gif)
16
+
17
+ ---
18
+
19
+ ## โšก The Problem It Solves
20
+
21
+ Large Language Models (LLMs) are notoriously bad at spatial reasoning and Cartesian mathematics:
22
+ - **Geometry Hallucination**: Self-intersecting polygons (bow-tie / figure-8 errors) and unclosed coordinate rings crash downstream GIS pipelines and map renderers.
23
+ - **Scale Distortion**: Treating spherical WGS84 geographic coordinates (latitude/longitude degrees) as flat Euclidean planes results in catastrophic area and perimeter errors (often 30% to 500% off depending on latitude).
24
+ - **Topology Blindness**: Inability to deterministically resolve point-in-polygon containment, spatial intersection surfaces, separation distances, and metric buffering.
25
+
26
+ `geospatial-mcp` bridges this gap by providing a lightweight, high-performance, GEOS-backed C-extension engine without heavy GIS infrastructure overhead.
27
+
28
+ ---
29
+
30
+ ## ๐Ÿš€ Quickstart (30 Seconds)
31
+
32
+ ### Option 1: Run with UVX (Recommended, Zero Install)
33
+ No local repository clone or manual environment setup required:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "geospatial": {
39
+ "command": "uvx",
40
+ "args": ["geospatial-mcp"]
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### Option 2: Local Installation & Development
47
+
48
+ 1. **Clone and create virtual environment**:
49
+ ```bash
50
+ git clone https://github.com/your-org/geospatial-mcp.git
51
+ cd geospatial-mcp
52
+
53
+ python -m venv .venv
54
+ # Windows (PowerShell):
55
+ .\.venv\Scripts\Activate.ps1
56
+ # Linux / macOS:
57
+ source .venv/bin/activate
58
+ ```
59
+
60
+ 2. **Install package in editable mode**:
61
+ ```bash
62
+ pip install -e .
63
+ pip install pytest
64
+ ```
65
+
66
+ 3. **Configure MCP Client (Claude Desktop / Cursor / Antigravity)**:
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "geospatial": {
71
+ "command": "python",
72
+ "args": ["-m", "geospatial_mcp.server"],
73
+ "cwd": "/path/to/geospatial-mcp"
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## ๐Ÿ› ๏ธ Tools Reference
82
+
83
+ All tools are exposed via standard MCP transports (stdio) and feature robust input tolerance: they natively accept standard GeoJSON strings, raw coordinate arrays, GeoJSON dictionaries, Feature / FeatureCollection objects, and Markdown-wrapped JSON (` ```json `) without crashing.
84
+
85
+ ### 1. `validate_and_repair_geometry`
86
+ Validates topological compliance against OGC Simple Features standards. If topological defects exist (e.g., self-intersecting bow-tie polygons or unclosed linear rings), it extracts the exact cause of failure and automatically heals the geometry into a compliant GeoJSON object with RFC 7946 normalized ring winding.
87
+
88
+ - **Parameters**:
89
+ - `geojson` (`str | dict | list`, required): GeoJSON geometry, Feature, FeatureCollection, or raw coordinate array.
90
+ - **Returns**:
91
+ - `is_valid` (`bool`): Whether the original geometry was topologically valid.
92
+ - `error_reason` (`str | null`): Exact topological defect description if invalid (e.g. `Self-intersection[1 1]` or `Points of LinearRing do not form a closed linestring`).
93
+ - `repaired_geojson` (`dict | null`): Compliant, self-healed GeoJSON geometry.
94
+
95
+ #### Example Call
96
+ ```json
97
+ {
98
+ "name": "validate_and_repair_geometry",
99
+ "arguments": {
100
+ "geojson": {
101
+ "type": "Polygon",
102
+ "coordinates": [[[0, 0], [2, 2], [2, 0], [0, 2], [0, 0]]]
103
+ }
104
+ }
105
+ }
106
+ ```
107
+ #### Response
108
+ ```json
109
+ {
110
+ "is_valid": false,
111
+ "error_reason": "Self-intersection[1 1]",
112
+ "repaired_geojson": {
113
+ "type": "MultiPolygon",
114
+ "coordinates": [
115
+ [[[0.0, 0.0], [1.0, 1.0], [0.0, 2.0], [0.0, 0.0]]],
116
+ [[[1.0, 1.0], [2.0, 0.0], [2.0, 2.0], [1.0, 1.0]]]
117
+ ]
118
+ }
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 2. `calculate_accurate_metrics`
125
+ Eliminates spherical degree distortion by deriving the optimal local UTM projection zone (`EPSG:32601-32660` for Northern hemisphere or `EPSG:32701-32760` for Southern hemisphere) from the polygon centroid, projecting to a planar Cartesian coordinate system via PyProj, and calculating distortion-free surface area and perimeter.
126
+
127
+ - **Parameters**:
128
+ - `geojson` (`str | dict | list`, required): Polygon or MultiPolygon GeoJSON.
129
+ - `unit` / `target_unit` (`str`, optional, default `"sq_meters"`): Desired unit (`"sq_meters"`, `"sq_kilometers"`, or `"hectares"`). Fully backwards-compatible with dual alias parsing.
130
+ - **Returns**:
131
+ - `area` (`float`): Planar area in the requested unit.
132
+ - `perimeter_meters` (`float`): Total boundary length in meters.
133
+ - `unit` (`str`): Target unit used.
134
+ - `projected_epsg` (`int`): EPSG integer code of the metric UTM projection applied (e.g. `32651`).
135
+
136
+ #### Example Call
137
+ ```json
138
+ {
139
+ "name": "calculate_accurate_metrics",
140
+ "arguments": {
141
+ "geojson": {
142
+ "type": "Polygon",
143
+ "coordinates": [[[121.47, 31.23], [121.48, 31.23], [121.48, 31.24], [121.47, 31.24], [121.47, 31.23]]]
144
+ },
145
+ "unit": "hectares"
146
+ }
147
+ }
148
+ ```
149
+ #### Response
150
+ ```json
151
+ {
152
+ "area": 105.74,
153
+ "perimeter_meters": 4118.62,
154
+ "unit": "hectares",
155
+ "projected_epsg": 32651
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 3. `check_spatial_relationship`
162
+ Evaluates topological relationship predicates between two geometries (`contains`, `within`, `intersects`, `disjoint`). Automatically returns the shared intersection area in $m^2$ if overlapping, or the minimum metric separation distance in meters if disjoint.
163
+
164
+ - **Parameters**:
165
+ - `geom_a` (`str | dict | list`, required): First spatial object.
166
+ - `geom_b` (`str | dict | list`, required): Second spatial object.
167
+ - `predicate` (`str`, optional, default `"intersects"`): Predicate to evaluate (`"contains"`, `"within"`, `"intersects"`, `"disjoint"`).
168
+ - **Returns**:
169
+ - `result` (`bool`): Boolean verdict for the requested predicate.
170
+ - `details` (`dict`): Quantitative measurements including `intersection_area_sq_meters`, `distance_meters`, and `projected_epsg`.
171
+
172
+ #### Example Call
173
+ ```json
174
+ {
175
+ "name": "check_spatial_relationship",
176
+ "arguments": {
177
+ "geom_a": {"type": "Polygon", "coordinates": [[[0, 0], [2, 0], [2, 2], [0, 2], [0, 0]]]},
178
+ "geom_b": {"type": "Polygon", "coordinates": [[[1, 1], [3, 1], [3, 3], [1, 3], [1, 1]]]},
179
+ "predicate": "intersects"
180
+ }
181
+ }
182
+ ```
183
+ #### Response
184
+ ```json
185
+ {
186
+ "result": true,
187
+ "details": {
188
+ "predicate": "intersects",
189
+ "projected_epsg": 32631,
190
+ "intersection_area_sq_meters": 12285149372.45,
191
+ "distance_meters": 0.0
192
+ }
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ### 4. `generate_buffer`
199
+ Constructs an accurate metric buffer (expansion or erosion) in the local UTM coordinate system, auto-heals any resulting self-intersections, and projects back to standard EPSG:4326 WGS84 GeoJSON.
200
+
201
+ - **Parameters**:
202
+ - `geojson` (`str | dict | list`, required): Input geometry.
203
+ - `distance_meters` (`float`, required): Buffer radius in meters (positive expands, negative erodes).
204
+ - `quad_segs` (`int`, optional, default `8`): Quadrant segments for circular arc approximation.
205
+ - **Returns**:
206
+ - `buffered_geojson` (`dict | null`): Normalized RFC 7946 GeoJSON dictionary in EPSG:4326.
207
+
208
+ #### Example Call
209
+ ```json
210
+ {
211
+ "name": "generate_buffer",
212
+ "arguments": {
213
+ "geojson": {"type": "Point", "coordinates": [121.5, 31.2]},
214
+ "distance_meters": 500.0,
215
+ "quad_segs": 16
216
+ }
217
+ }
218
+ ```
219
+
220
+ ---
221
+
222
+ ## ๐Ÿ—๏ธ Architecture & Project Structure
223
+
224
+ `geospatial-mcp` is designed to be completely decoupled from bulky C/C++ GIS frameworks:
225
+
226
+ ```
227
+ geospatial-mcp/
228
+ โ”œโ”€โ”€ pyproject.toml # Hatchling packaging and dependency constraints
229
+ โ”œโ”€โ”€ README.md # Complete architectural guide and tool references
230
+ โ”œโ”€โ”€ src/
231
+ โ”‚ โ””โ”€โ”€ geospatial_mcp/
232
+ โ”‚ โ”œโ”€โ”€ __init__.py # Package root
233
+ โ”‚ โ”œโ”€โ”€ server.py # FastMCP service instance & tool entrypoints
234
+ โ”‚ โ”œโ”€โ”€ core/ # Pure C-extension geometry engine
235
+ โ”‚ โ”‚ โ”œโ”€โ”€ __init__.py
236
+ โ”‚ โ”‚ โ”œโ”€โ”€ projection.py # Dynamic UTM zone deduction & CRS reprojector
237
+ โ”‚ โ”‚ โ””โ”€โ”€ geometry.py # Topology validation, metric calculations, relations & buffer
238
+ โ”‚ โ””โ”€โ”€ models.py # Pydantic v2 schemas (unit / target_unit alias support)
239
+ โ””โ”€โ”€ tests/
240
+ โ”œโ”€โ”€ __init__.py
241
+ โ”œโ”€โ”€ test_geometry.py # Core geometry, projection, bowtie repair, equator tests
242
+ โ””โ”€โ”€ test_server.py # FastMCP tool invocation, input tolerance, & error tests
243
+ ```
244
+
245
+ ### Why Zero GDAL?
246
+ Traditional GIS tools bundle GDAL, PROJ C libraries, and Python wrappers (Fiona, GeoPandas) that create:
247
+ - Massive container image sizes (> 1.5 GB).
248
+ - Platform-dependent C++ build failures and DLL hell on Windows / macOS.
249
+ - Potential process segmentation faults during concurrent execution.
250
+
251
+ `geospatial-mcp` uses pure **`shapely>=2.0`** (which packages standalone GEOS C libraries as pre-compiled wheels) and **`pyproj`**. It installs in seconds via `pip` or `uvx` with zero compilation steps.
252
+
253
+ ---
254
+
255
+ ## ๐Ÿ›ก๏ธ Fault Tolerance & Defensive Engineering
256
+
257
+ 1. **Unclosed Linear Rings**: In RFC 7946, polygon exterior and interior rings must be closed (first coordinate identical to last coordinate). If an unclosed ring is passed, the engine flags it (`is_valid=False`), describes the error, and automatically closes the ring during repair.
258
+ 2. **Malformed JSON / Syntax Errors**: Broken JSON strings, unclosed brackets, or invalid geometry types return structured, friendly JSON error responses with diagnostic hints rather than crashing the MCP server.
259
+ 3. **Hemisphere & Longitude Aware**: Automatically maps any coordinate on Earth into its corresponding UTM Zone (1 through 60) and applies EPSG:326xx for $lat \ge 0^\circ$ (North) and EPSG:327xx for $lat < 0^\circ$ (South), with seamless handling for geometries crossing the equator.
260
+ 4. **Dual Access Output Models**: All output objects implement `BaseResultModel`, permitting both object attribute access (`res.area`) and dictionary key access (`res['area']`).
261
+
262
+ ---
263
+
264
+ ## ๐Ÿงช Testing & Verification
265
+
266
+ The suite includes 20 comprehensive unit tests covering critical GIS boundary scenarios:
267
+
268
+ ```bash
269
+ # Run tests with pytest
270
+ pytest -v
271
+ ```
272
+
273
+ ### Verified Scenarios
274
+ - [x] **Bow-tie Self-Intersection Repair**: Resolves figure-8 polygons into valid OGC MultiPolygons.
275
+ - [x] **Unclosed Ring Auto-Healing**: Detects unclosed coordinate arrays and normalizes them.
276
+ - [x] **Equator Crossing & Multi-Longitude UTM**: Precision reprojection across latitude $0^\circ$ and longitudinal zones (Shanghai UTM 51N, Rio de Janeiro UTM 23S).
277
+ - [x] **Unit Conversions**: Metric validation across square meters, square kilometers, and hectares.
278
+ - [x] **Spatial Predicates**: Accurate metric distance on separation, intersection surface area on overlap.
279
+ - [x] **Parameter Alias Compatibility**: Identical resolution whether calling with `unit` or `target_unit`.
280
+ - [x] **Stdio Protocol Handshake**: Seamless MCP `initialize` JSON-RPC handshake verification.
281
+
282
+ ---
283
+
284
+ ## ๐Ÿ“„ License
285
+
286
+ MIT License. Free for open-source and commercial use.
@@ -0,0 +1,11 @@
1
+ Output docs/assets/demo.gif
2
+ Set FontSize 16
3
+ Set Width 1200
4
+ Set Height 600
5
+ Set Theme "Catppuccin Mocha"
6
+
7
+ Type "python -m geospatial_mcp.server"
8
+ Enter
9
+ Sleep 1s
10
+ Type "# Geospatial MCP Server running on stdio..."
11
+ Sleep 2s