Skip to main content
Glama
tharlestsa

OpenLandMap MCP Server

by tharlestsa
README.md
# 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

A3.6/5.0

Scored across 27 tools

Disambiguation3/5

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.

Naming Consistency5/5

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.

Tool Count2/5

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.

Completeness4/5

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.

Maintenance

ActivityInactive
ResponsivenessNo issues