OpenLandMap MCP Server
# OpenLandMap MCP Server
MCP Server for the [OpenLandMap](https://openlandmap.org) STAC catalog — full access to global geospatial environmental data for AI agents.
## Overview
OpenLandMap provides **104+ collections** of global environmental data as Cloud-Optimized GeoTIFFs (COG), covering:
| Theme | Examples |
|-------|----------|
| Soil | Organic carbon, pH, texture, bulk density, water content |
| Vegetation | EVI, FAPAR, forest cover, plant functional types |
| Land Cover | Land cover, land use, cropland, pasture, urban areas |
| Climate | Land surface temperature (LST), precipitation, bioclim |
| Terrain | DEM, slope, aspect, curvature, geomorphometry |
| Water | Water occurrence, snow cover |
| Atmosphere | NO2, water vapor, aerosol optical depth |
| Population | Population density, human footprint, wilderness |
## Installation
```bash
cd openlandmap-mcp
# Install with uv
uv pip install -e .
# Or run directly (uv resolves dependencies automatically)
uv run openlandmap-mcp
```
## Configuration
> **Prerequisites:** [uv](https://docs.astral.sh/uv/) must be installed and available in `PATH`.
### Claude Code
Add to `.claude/settings.json` (project-level) or `~/.claude/settings.json` (global):
```json
{
"mcpServers": {
"openlandmap": {
"command": "uv",
"args": ["run", "--directory", "/path/to/openlandmap-mcp", "openlandmap-mcp"]
}
}
}
```
### Claude Desktop
Add to your `claude_desktop_config.json`:
| OS | Path |
|----|------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
```json
{
"mcpServers": {
"openlandmap": {
"command": "uv",
"args": ["run", "--directory", "/path/to/openlandmap-mcp", "openlandmap-mcp"]
}
}
}
```
### Gemini CLI
Add to `~/.gemini/settings.json` (global) or `.gemini/settings.json` (project-level):
| OS | Global path |
|----|-------------|
| Linux / macOS | `~/.gemini/settings.json` |
| Windows | `%USERPROFILE%\.gemini\settings.json` |
```json
{
"mcpServers": {
"openlandmap": {
"command": "uv",
"args": ["run", "--directory", "/path/to/openlandmap-mcp", "openlandmap-mcp"],
"timeout": 60000
}
}
}
```
After configuration, verify with the `/mcp` command inside Gemini CLI to list connected servers and available tools.
### ChatGPT Desktop
ChatGPT supports MCP servers exclusively via **remote HTTP/SSE transport** — local stdio processes are not supported. You need to expose the server over HTTP first.
**Step 1 — Start the server with SSE transport:**
```bash
uv run openlandmap-mcp --transport sse --port 8811
```
**Step 2 — Expose locally (for development) or deploy publicly:**
For local development, use a tunnel like [ngrok](https://ngrok.com):
```bash
ngrok http 8811
# Example output: https://abc123.ngrok.app
```
For production, deploy behind a reverse proxy with a public HTTPS URL.
**Step 3 — Register in ChatGPT Desktop:**
1. Open **Settings → Apps & Connectors → Advanced Settings**
2. Enable **Developer Mode**
3. Go to **Settings → Connectors → Create**
4. Fill in:
- **Name:** `OpenLandMap`
- **Connector URL:** `https://abc123.ngrok.app/mcp` (or your public URL)
- **Authentication:** None (or configure as needed)
> **Note:** ChatGPT does not support local config files for MCP. All registration is done through the UI. The server must be reachable over HTTPS.
### Cursor / VS Code
Add to `.cursor/mcp.json` (Cursor) or `.vscode/mcp.json` (VS Code):
```json
{
"mcpServers": {
"openlandmap": {
"command": "uv",
"args": ["run", "--directory", "/path/to/openlandmap-mcp", "openlandmap-mcp"]
}
}
}
```
### Compatibility Matrix
| Client | Transport | Config file | Local stdio |
|--------|-----------|-------------|-------------|
| Claude Code | stdio | `.claude/settings.json` | Yes |
| Claude Desktop | stdio | `claude_desktop_config.json` | Yes |
| Gemini CLI | stdio | `~/.gemini/settings.json` | Yes |
| ChatGPT Desktop | HTTP/SSE | UI only (no file) | No |
| Cursor | stdio | `.cursor/mcp.json` | Yes |
| VS Code | stdio | `.vscode/mcp.json` | Yes |
---
## Capabilities
### 1. Catalog Discovery
Browse and search the entire OpenLandMap catalog.
#### `catalog_info()`
Returns root catalog metadata: ID, description, STAC version, total collection count.
```
"What data is available on OpenLandMap?"
```
#### `list_collections(theme?, keyword?, limit?)`
List collections with optional filters by theme alias or keyword search across titles, descriptions, and keywords.
```
"List all soil collections"
→ list_collections(theme="soil")
"Find collections related to organic carbon"
→ list_collections(keyword="organic carbon")
"Show the first 5 vegetation datasets"
→ list_collections(theme="vegetation", limit=5)
```
**Available theme aliases:** `soil`, `vegetation`, `land_cover`, `climate`, `terrain`, `water`, `atmosphere`, `population`
#### `get_collection_schema(collection_id)`
Returns the full schema of a collection: asset type definitions (MIME types, roles), STAC extensions used, spatial/temporal extent, contact info, style URLs (SLD/QML), and related links.
```
"Show me everything about the organic carbon collection"
→ get_collection_schema("organic.carbon_usda.6a1c")
```
#### `discover_data_for_topic(topic)`
Natural language search across all collection titles, descriptions, and keywords. Returns results ranked by relevance score.
```
"Find data about deforestation in the Amazon"
→ discover_data_for_topic("Amazon deforestation forest loss")
"What datasets exist for air quality analysis?"
→ discover_data_for_topic("air quality NO2 aerosol")
```
---
### 2. Collection Operations
Analyze, compare, and explore collections in depth.
#### `compare_collections(collection_ids)`
Side-by-side comparison of 2–10 collections: temporal coverage, resolution, units, keywords, and theme.
```
"Compare soil organic carbon and pH datasets"
→ compare_collections(["organic.carbon_usda.6a1c", "ph.h2o_usda.4c1a2a"])
```
#### `get_collection_temporal_stats(collection_id)`
Temporal statistics: earliest/latest dates, item count, year-by-year distribution, and median update interval.
```
"How often is the EVI dataset updated?"
→ get_collection_temporal_stats("evi_mod13q1.tmwm.inpaint")
```
#### `find_related_collections(collection_id)`
Finds collections sharing the same theme or native category. Useful for discovering complementary datasets.
```
"What other datasets are related to land surface temperature?"
→ find_related_collections("lst_mod11a2.daytime")
```
#### `get_soil_collections()`
Shortcut returning all soil-related collections (bulk density, organic carbon, pH, sand/clay/silt, texture, water content, taxonomy).
```
"List all available soil datasets"
→ get_soil_collections()
```
#### `get_vegetation_collections()`
Shortcut returning all vegetation-related collections (EVI, FAPAR, forest cover, plant functional types).
```
"What vegetation indices are available?"
→ get_vegetation_collections()
```
#### `get_land_cover_collections()`
Shortcut returning all land cover/land use collections (classification, cropland, pasture, urban, change detection).
```
"Show me land cover datasets"
→ get_land_cover_collections()
```
---
### 3. Item Search & Access
Search and retrieve individual temporal snapshots within collections.
#### `search_items(collection_id, bbox?, datetime_range?, limit?, offset?)`
Search items with spatial (bounding box) and temporal (ISO 8601 interval) filters. Supports pagination.
```
"Find organic carbon data for the Cerrado region"
→ search_items("organic.carbon_usda.6a1c", bbox=[-60.47, -24.68, -41.28, -2.33])
"Get EVI data from 2015 to 2020"
→ search_items("evi_mod13q1.tmwm.inpaint", datetime_range="2015-01-01/2020-12-31", limit=10)
"Show all items in the land cover collection"
→ search_items("land.cover_esacci.lc.l4", limit=50)
```
#### `get_item_detail(collection_id, item_id)`
Full item details: geometry (GeoJSON), bounding box, temporal range, all assets with resolved URLs, MIME types, file sizes, and checksums.
```
"Show full details for the 2018 organic carbon item"
→ get_item_detail("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231")
```
#### `list_items_temporal(collection_id)`
Lists all items sorted chronologically. Shows the complete temporal coverage and update pattern of a dataset.
```
"What time periods are available for EVI?"
→ list_items_temporal("evi_mod13q1.tmwm.inpaint")
```
#### `find_items_by_point(lon, lat, collection_ids?)`
Given a WGS84 coordinate, finds all items covering that point across one or more collections.
```
"What data is available for Brasília?"
→ find_items_by_point(-47.9, -15.8)
"Find soil data at this location"
→ find_items_by_point(-47.9, -15.8, ["organic.carbon_usda.6a1c", "ph.h2o_usda.4c1a2a"])
```
---
### 4. Asset Access & Download
Resolve URLs and generate download instructions for data files.
#### `get_asset_url(collection_id, item_id, asset_key)`
Resolves the full S3 URL for a specific asset. Returns MIME type, roles, COG status, file size, checksum, and S3 bucket/key.
```
"Get the URL for the 0cm depth organic carbon layer"
→ get_asset_url("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231", "organic.carbon_usda.6a1c_m_250m_b0cm")
```
#### `list_assets_for_item(collection_id, item_id)`
Lists all assets (data layers, thumbnails, style files) with complete metadata for each.
```
"What files are available in this item?"
→ list_assets_for_item("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231")
```
#### `get_all_data_assets(collection_id, item_id)`
Filters to only data assets (role=`"data"`), excluding thumbnails, QML, and SLD files. Returns direct COG URLs.
```
"Give me only the data layers, not the styles"
→ get_all_data_assets("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231")
```
#### `get_asset_download_info(collection_id, item_id, asset_key)`
Returns the direct URL plus ready-to-use commands and code snippets:
- **curl** command for download
- **wget** command for download
- **Python** snippet using `rasterio` (streaming, no download needed)
- **R** snippet using `terra` (streaming, no download needed)
```
"How do I access the soil carbon data programmatically?"
→ get_asset_download_info("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231", "organic.carbon_usda.6a1c_m_250m_b0cm")
```
#### `get_visualization_assets(collection_id, item_id)`
Returns visualization-related assets: thumbnails (PNG), QGIS layer styles (QML), and WMS style descriptors (SLD).
```
"Get the QGIS style file for this layer"
→ get_visualization_assets("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231")
```
---
### 5. Spatial Queries
Work with geographic regions and spatial coverage.
#### `get_bbox_for_region(region_name)`
Returns the WGS84 bounding box `[west, south, east, north]` for a named region. Supports ~50 regions:
- **Countries:** `brazil`, `usa`, `china`, `india`, `australia`, `germany`, `france`, `japan`, `mexico`, `argentina`, `colombia`, `south_africa`, `kenya`, `indonesia`, etc.
- **Brazilian states:** `goias`, `mato_grosso`, `para`, `minas_gerais`, `sao_paulo`, `bahia`, `amazonas`, `tocantins`, `maranhao`, `rondonia`, `acre`, `roraima`, `amapa`, etc.
- **Brazilian biomes:** `cerrado`, `amazon`, `amazonia_legal`, `caatinga`, `pantanal`, `mata_atlantica`, `pampa`
- **Continents/regions:** `south_america`, `north_america`, `europe`, `africa`, `asia`, `southeast_asia`, `oceania`, `middle_east`, `global`
```
"What's the bounding box for the Cerrado biome?"
→ get_bbox_for_region("cerrado")
# Returns: {"region": "cerrado", "bbox": [-60.47, -24.68, -41.28, -2.33]}
"Get the bbox for Goiás state"
→ get_bbox_for_region("goias")
```
#### `find_collections_for_bbox(bbox, theme?)`
Finds all collections whose spatial extent intersects the given bounding box. Optionally filters by theme.
```
"What soil data covers the Cerrado?"
→ find_collections_for_bbox([-60.47, -24.68, -41.28, -2.33], theme="soil")
"Find all datasets available for Southeast Asia"
→ find_collections_for_bbox([92.0, -11.0, 141.0, 28.0])
```
#### `get_collection_spatial_coverage(collection_id)`
Returns full spatial coverage: bounding box, GeoJSON polygon of the extent, ground sample distance (resolution in meters), and CRS.
```
"What is the spatial resolution of the elevation model?"
→ get_collection_spatial_coverage("dtm.bareearth_ensemble")
```
---
### 6. Analysis & Code Generation
Discover data by topic, analyze temporal coverage, and generate ready-to-use code.
#### `get_data_timeline(collection_ids)`
Generates a structured timeline showing data availability across 1–10 collections. Useful for planning multi-temporal analyses.
```
"Show me when soil and vegetation data overlap"
→ get_data_timeline(["organic.carbon_usda.6a1c", "evi_mod13q1.tmwm.inpaint"])
```
#### `find_overlapping_datasets(collection_id, start_year, end_year)`
Finds all other collections with temporal overlap in the given period. Useful for multi-variable correlation studies.
```
"What datasets overlap with land cover data between 2010 and 2020?"
→ find_overlapping_datasets("land.cover_esacci.lc.l4", 2010, 2020)
```
#### `get_stac_item_as_geojson(collection_id, item_id)`
Returns the item as a pure GeoJSON Feature, ready for use in any GIS tool, web map, or geospatial pipeline.
```
"Export this item as GeoJSON"
→ get_stac_item_as_geojson("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231")
```
#### `build_python_snippet(collection_id, item_id, asset_key, operation)`
Generates a ready-to-run Python code snippet using `rasterio` and `matplotlib`.
**Operations:** `open`, `info`, `plot`, `clip_bbox`, `stats`, `export_csv`
```
"Generate Python code to plot organic carbon data"
→ build_python_snippet("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231", "organic.carbon_usda.6a1c_m_250m_b0cm", "plot")
"Generate code to compute statistics"
→ build_python_snippet(..., operation="stats")
"Generate code to clip to a bounding box"
→ build_python_snippet(..., operation="clip_bbox")
```
#### `build_r_snippet(collection_id, item_id, asset_key, operation)`
Generates a ready-to-run R code snippet using `terra` and `sf`.
**Operations:** `open`, `info`, `plot`, `clip_bbox`, `stats`, `export_csv`
```
"Generate R code to open and plot this raster"
→ build_r_snippet("organic.carbon_usda.6a1c", "organic.carbon_usda.6a1c_19500101_20171231", "organic.carbon_usda.6a1c_m_250m_b0cm", "plot")
```
---
## MCP Resources
Static data accessible via URI patterns:
| URI | Description |
|-----|-------------|
| `stac://openlandmap/catalog` | Root catalog metadata (ID, version, collection count) |
| `stac://openlandmap/themes` | Index of all collections organized by thematic category |
| `stac://openlandmap/guide` | Usage guide with quick-start instructions |
| `stac://openlandmap/collection/{id}` | Full metadata for a specific collection |
| `stac://openlandmap/collection/{id}/item/{item_id}` | Full item details including all assets |
## MCP Prompts
Pre-built workflow templates for common analysis patterns:
| Prompt | Description | Parameters |
|--------|-------------|------------|
| `explore_collection` | Step-by-step exploration of a collection (schema, temporal coverage, assets, related data) | `collection_id` |
| `find_data_for_analysis` | Guided workflow to find data for a specific analysis (region + topic + period) | `topic`, `region`, `period` |
| `download_workflow` | Complete data access workflow with URLs, commands, and code snippets | `collection_id`, `item_id` |
---
## Usage Examples
```
"What data is available on OpenLandMap?"
"List all soil datasets"
"Find collections about organic carbon"
"Show the temporal coverage of evi_mod13q1.tmwm.inpaint"
"Find vegetation data for the Cerrado between 2015 and 2020"
"Generate a Python snippet to access EVI data"
"Compare soil collections: organic.carbon vs ph.h2o"
"What is the spatial resolution of the elevation dataset?"
"Find datasets that overlap temporally with land cover between 2010-2020"
"Generate R code to plot soil organic carbon"
"What data is available at coordinates -47.9, -15.8?"
"Export this STAC item as GeoJSON for use in QGIS"
"How do I download the bulk density dataset with curl?"
"Show me the QGIS style file for the pH collection"
```
## Architecture
- **Static catalog:** Data is served from S3 (Wasabi) as static STAC JSONs — no server-side search API
- **In-memory cache:** Configurable TTL per resource type (1h catalog, 30min collections, 10min items)
- **Lazy loading:** Collections fetched on demand with concurrent batch fetch (semaphore-limited) when global filtering is needed
- **No native dependencies:** Pure Python and pre-built wheels only (no GDAL/GEOS compilation required)
- **Retry with backoff:** Automatic retry (3 attempts, exponential delays 1s/2s/4s) for all HTTP requests
- **Thundering herd protection:** Per-key async locks prevent duplicate fetches for the same resource
## License
OpenLandMap data is distributed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
TDQS
Scored across 27 tools
Most tools have distinct purposes, but there is some overlap that could cause confusion. For example, get_asset_url and get_asset_download_info both provide asset URLs, and list_assets_for_item overlaps with get_all_data_assets and get_visualization_assets in listing assets. The descriptions help differentiate, but an agent might struggle to choose between these similar tools.
Tool names follow a consistent verb_noun pattern throughout, such as build_python_snippet, catalog_info, compare_collections, and find_collections_for_bbox. There are no deviations in naming conventions, making the set predictable and easy to understand.
With 27 tools, the count is too high for the server's purpose of accessing and exploring geospatial data. Many tools could be consolidated (e.g., multiple asset-listing tools) or omitted without losing functionality, leading to potential overwhelm and inefficiency for agents.
The toolset provides comprehensive coverage for discovering, accessing, and analyzing OpenLandMap data, including catalog browsing, spatial/temporal queries, asset retrieval, and code generation. Minor gaps exist, such as no direct data manipulation or advanced analysis tools, but core workflows are well-supported.