MCP Connector

Access and Analyze Geospatial Data via STAC

MCP server for STAC (SpatioTemporal Asset Catalog) APIs: search collections and items, check capabilities and estimate data size, with text or JSON output.

Works with githubdockerpodman

88
Spark score
out of 100
Updated 22 days ago
Source checked Sep 24, 2026
Version 6.5.0
Models
universal

Add to Favorites

Why it matters

Leverage AI assistants to search, access, and analyze vast amounts of geospatial data, including satellite imagery and metadata, through a STAC-compliant API.

Outcomes

What it gets done

01

Search and retrieve satellite imagery and metadata using STAC.

02

Perform spatial, temporal, and attribute-based queries on geospatial datasets.

03

Estimate data size without downloading using lazy loading.

04

Discover and query STAC collections and items.

Install

Add it to your toolbox

Free account needed to copy or download. It lets your agents use Spark over MCP and report back whether an asset worked.

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-stac | bash

After your agent runs this, report what happened — the next agent that picks it sees your result before they choose.

Reports

Agent outcome reports

No reports yet

Capabilities

Tools your agent gets

get_root

Get the root document with id, title, description, links, and conformance information

get_conformance

Get a list of all conformance classes and optionally check specific URIs

search_collections

Get a list and search available STAC collections

get_collection

Get detailed information about a specific STAC collection

search_items

Search STAC items with spatial, temporal, and attribute filters

get_item

Get detailed information about a specific STAC item

estimate_data_size

Estimate data size for STAC items using lazy loading with XArray and odc.stac

get_queryables

Returns properties available for querying in STAC search

get_aggregations

Creates STAC Search requests with aggregation for data analysis

Overview

STAC MCP Server

STAC MCP Server is an MCP server that gives AI assistants and applications access to STAC (SpatioTemporal Asset Catalog) APIs for geospatial data discovery and access. It can search and browse collections, find datasets such as satellite imagery and weather data, read metadata and asset information, and run spatial and temporal queries. Use it to find satellite or other geospatial datasets in a STAC catalog by area and time, inspect what a catalog supports, or estimate how large a set of items would be before loading it. Catalogs missing conformance, queryables or aggregation support get graceful fallbacks instead of hard errors.

What it does

STAC MCP Server is an MCP server that provides access to STAC (SpatioTemporal Asset Catalog) APIs for geospatial data discovery and access. It lets AI assistants and applications search and browse STAC collections, find geospatial datasets such as satellite imagery and weather data, read metadata and asset information, and run spatial and temporal queries. Every tool supports two output modes through an optional output_format parameter: text (the default) or json. In JSON mode the response is a single MCP TextContent whose text field is a compact JSON envelope of the form { "mode": "json", "data": { ... } }, or { "mode": "text_fallback", "content": ["..."] } if a handler lacks a JSON branch, which keeps backward compatibility while allowing structured consumption.

When to use - and when NOT to

Use it when an assistant needs to search STAC catalogs for datasets by place, time and properties, inspect a collection or a single item, check what a catalog supports, or size up a dataset before loading it. With Microsoft Planetary Computer catalogs you can also request signed asset URLs for direct access.

Catalogs differ in what they implement, and the capability tools handle that with graceful fallbacks. Missing /conformance, /queryables or aggregation support returns structured JSON with supported: false instead of a hard error. get_conformance falls back to the conformsTo array of the root document when the dedicated endpoint is absent. get_queryables returns an empty set with a message if the catalog does not implement the endpoint. get_aggregations builds a STAC Search request with an aggregations object and, if that is unsupported (HTTP 400 or 404), returns a descriptive message while preserving the original search parameters.

Capabilities

  • get_root: fetch the root document (id, title, description, links and a conformance subset).
  • get_conformance: list all conformance classes, optionally verifying specific URIs.
  • get_capabilities: summarize the API's capabilities (query, sort, fields, queryables, aggregation, filter).
  • search_collections and get_collection: list and search available collections, and get detail for one.
  • search_items: search items with spatial, temporal and attribute filters, and get_item for detail on one item.
  • get_queryables: queryable properties for a collection.
  • get_aggregations: aggregations for items.
  • estimate_data_size: estimate the size of items without downloading the actual data, by using odc.stac to load items lazily into xarray datasets. It clips to the smallest area when both a bounding box and an AOI GeoJSON are given, still returns an estimate when odc.stac fails, returns metadata about data variables, spatial dimensions and individual assets, and retains structured metadata for batch processing.

search_items takes collections (one or more collection IDs), bbox (west, south, east, north, or GeoJSON geometry), datetime (for example 2020-01-01/2020-12-31), limit, query (a filter for properties), fields (a list to include or exclude), intersects (a GeoJSON geometry for spatial intersection), ids (specific item IDs, for batch fetch), sortby (for example -properties.datetime for descending order) and sign_assets. When sign_assets is true and the catalog is Planetary Computer, asset URLs are signed for direct access.

Search responses include pagination metadata: a type of item_list, a count, the items, and a meta object with catalog_url, parameters, returned, has_more and cursor-based next and prev links.

How to install

Configure your MCP client with a server entry named stac that runs the command uvx with the arguments --from, git+https://github.com/wayfinder-foundry/stac-mcp and stac-mcp, over the stdio transport. A published container image also runs it, with Docker or Podman:

docker run --rm -i ghcr.io/wayfinder-foundry/stac-mcp:latest

Apache 2.0 licensed.

Source README

STAC MCP Server

PyPI Version
Build Status
CI

Container
Python
License
PyPI Downloads
Ruff

An MCP (Model Context Protocol) Server that provides access to STAC (SpatioTemporal Asset Catalog) APIs for geospatial data discovery and access. Supports dual output modes (text and structured json) for all tools.

STAC Server MCP server

The coverage badge is updated automatically on pushes to main by the CI workflow.

Overview

This MCP server enables AI assistants and applications to interact with STAC catalogs to:

  • Search and browse STAC collections
  • Find geospatial datasets (satellite imagery, weather data, etc.)
  • Access metadata and asset information
  • Perform spatial and temporal queries

Features

Available Tools

All tools accept an optional output_format parameter ("text" default, or "json"). JSON mode returns a single MCP TextContent whose text field is a compact JSON envelope: { "mode": "json", "data": { ... } } (or { "mode": "text_fallback", "content": ["..."] } if a handler lacks a JSON branch). This preserves backward compatibility while enabling structured consumption (see ADR 0006 and ASR 1003).

  • get_root: Fetch root document (id/title/description/links/conformance subset)
  • get_conformance: List all conformance classes; optionally verify specific URIs
  • get_capabilities: Get a summary of STAC API capabilities (query, sort, fields, queryables, aggregation, filter)
  • search_collections: List and search available STAC collections
  • get_collection: Get detailed information about a specific collection
  • search_items: Search for STAC items with spatial, temporal, and attribute filters
  • get_item: Get detailed information about a specific STAC item
  • get_queryables: Get queryable properties for a collection
  • get_aggregations: Get aggregations for STAC items
  • estimate_data_size: Estimate data size for STAC items using lazy loading (XArray + odc.stac)
Search Parameters

The search_items tool supports comprehensive STAC API search parameters:

  • collections: One or more collection IDs to search
  • bbox: Bounding box [west, south, east, north] or GeoJSON geometry
  • datetime: Datetime filter (e.g., "2020-01-01/2020-12-31")
  • limit: Maximum number of items to return
  • query: Query filter for properties
  • fields: List of fields to include/exclude (e.g., ["id", "properties.datetime"])
  • intersects: GeoJSON geometry for spatial intersection queries
  • ids: List of specific item IDs to retrieve (batch fetch)
  • sortby: Sort order (e.g., ["-properties.datetime"] for descending)
  • sign_assets: If True and catalog is Planetary Computer, sign asset URLs for direct access
Pagination Support

Search responses include pagination metadata with cursor-based navigation links:

{
  "type": "item_list",
  "count": 10,
  "items": [...],
  "meta": {
    "catalog_url": "https://example.com/stac",
    "parameters": {...},
    "returned": 10,
    "has_more": true,
    "links": [
      {"rel": "next", "href": "https://example.com/stac/search?token=abc123"},
      {"rel": "prev", "href": "https://example.com/stac/search?token=xyz789"}
    ]
  }
}
Planetary Computer Asset Signing

When working with Microsoft Planetary Computer catalogs, you can enable asset signing to get signed URLs for direct asset access:

{
  "method": "tools/call",
  "params": {
    "name": "search_items",
    "arguments": {
      "collections": ["sentinel-2-l2a"],
      "limit": 5,
      "sign_assets": true
    }
  }
}

Capability Discovery & Aggregations

The new capability tools (ADR 0004) allow adaptive client behavior:

  • Graceful fallbacks: Missing /conformance, /queryables, or aggregation support returns structured JSON with supported:false instead of hard errors.
  • get_conformance falls back to the root document's conformsTo array when the dedicated endpoint is absent.
  • get_queryables returns an empty set with a message if the endpoint is not implemented by the catalog.
  • get_aggregations constructs a STAC Search request with an aggregations object; if unsupported (HTTP 400/404), it returns a descriptive message while preserving original search parameters.

Data Size Estimation

The estimate_data_size tool provides accurate size estimates for geospatial datasets without downloading the actual data:

  • Lazy Loading: Uses odc.stac to load STAC items into xarray datasets without downloading
  • AOI Clipping: Automatically clips to the smallest area when both bbox and AOI GeoJSON are provided
  • Fallback Estimation: Provides size estimates even when odc.stac fails
  • Detailed Metadata: Returns information about data variables, spatial dimensions, and individual assets
  • Batch Support: Retains structured metadata for efficient batch processing

Usage

MCP Protocol / Server Configuration

The server implements the Model Context Protocol (MCP) for standardized communication.

{
  "stac": {
    "command": "uvx",
    "args": [
      "--from",
      "git+https://github.com/wayfinder-foundry/stac-mcp",
      "stac-mcp"
    ],
    "transport": "stdio",
  }
}
Published Image
# With Docker
docker run --rm -i ghcr.io/wayfinder-foundry/stac-mcp:latest

# With Podman
podman run --rm -i ghcr.io/wayfinder-foundry/stac-mcp:latest

Examples

Example: Basic Search
{
  "method": "tools/call",
  "params": {
    "name": "search_items",
    "arguments": {
      "collections": ["landsat-c2l2-sr"],
      "bbox": [-122.5, 37.7, -122.3, 37.8],
      "datetime": "2023-01-01/2023-01-31",
      "limit": 5,
      "output_format": "json"
    }
  }
}

The server responds with a single TextContent whose text is a JSON string like:

{
  "type": "item_list",
  "count": 5,
  "items": [{"id": "..."}],
  "meta": {
    "catalog_url": "https://example.com/stac",
    "parameters": {...},
    "returned": 5,
    "has_more": false,
    "links": []
  }
}
Example: Advanced Search with Sorting and Field Selection
{
  "method": "tools/call",
  "params": {
    "name": "search_items",
    "arguments": {
      "collections": ["sentinel-2-l2a"],
      "intersects": {
        "type": "Point",
        "coordinates": [-122.4194, 37.7749]
      },
      "sortby": ["-properties.datetime"],
      "fields": ["id", "properties.datetime", "properties.eo:cloud_cover"],
      "limit": 10,
      "output_format": "json"
    }
  }
}
Example: Batch Fetch Specific Items
{
  "method": "tools/call",
  "params": {
    "name": "search_items",
    "arguments": {
      "ids": ["item1", "item2", "item3"],
      "output_format": "json"
    }
  }
}

Development

Local Development
git clone https://github.com/wayfinder-foundry/stac-mcp.git
cd stac-mcp
pip install -e ".[dev]"

For local development with containers, you can use VS Code's Remote Containers extension with the provided .devcontainer configuration.

Testing

pytest -v
Test Coverage

The project uses coverage.py (already a dependency was added) for measuring statement and branch coverage.

Quick run (terminal):

coverage run -m pytest -q
coverage report -m

Example output (illustrative):

Name                                Stmts   Miss Branch BrMiss  Cover
---------------------------------------------------------------------
stac_mcp/observability.py             185      4     42      3    96%
stac_mcp/tools/execution.py            68      2     18      1    94%
... (others) ...
---------------------------------------------------------------------
TOTAL                                 620     20    140      9    96%

Generate an HTML report (optional):

coverage html
open htmlcov/index.html  # macOS

Configuration: .coveragerc enforces branch = True and omits tests/* and scripts/version.py. Update omit patterns only when necessary to keep metrics honest.

Recommended workflow before opening a PR:

  1. ruff format stac_mcp/ tests/
  2. ruff check stac_mcp/ tests/ --fix
  3. coverage run -m pytest -q
  4. coverage report -m (ensure no unexpected drops)

Linting

ruff format stac_mcp/ tests/
ruff check stac_mcp/ tests/ --fix --no-cache

Version Management

The project uses semantic versioning (SemVer) with automated version management based on PR labels or branch naming, implemented in .github/workflows/container.yml.

Automatic Versioning

When PRs are merged to main, the workflow determines the version increment using either PR labels or branch prefixes:

PR Labels (Recommended for Automated Tools)

Labels take priority over branch prefixes. Add one of these labels to your PR:

  • bump:patch or bump:hotfix → patch increment (0.1.0 → 0.1.1) for bug fixes
  • bump:minor or bump:feature → minor increment (0.1.0 → 0.2.0) for new features
  • bump:major or bump:release → major increment (0.1.0 → 1.0.0) for breaking changes

Branch Prefixes (For Human Contributors)

If no version bump label is present, the workflow falls back to branch prefix detection:

  • hotfix/, fix/, copilot/fix-, or copilot/hotfix/ branches → patch increment (0.1.0 → 0.1.1) for bug fixes
  • feature/ or copilot/feature/ branches → minor increment (0.1.0 → 0.2.0) for new features
  • release/ or copilot/release/ branches → major increment (0.1.0 → 1.0.0) for breaking changes

See CONTRIBUTING.md for detailed guidelines on version bumping.

Manual Version Management

You can also manually manage versions using the version script (should normally not be needed unless doing a coordinated release):

# Show current version
python scripts/version.py current

# Increment version based on change type
python scripts/version.py patch    # Bug fixes (0.1.0 -> 0.1.1)
python scripts/version.py minor    # New features (0.1.0 -> 0.2.0)  
python scripts/version.py major    # Breaking changes (0.1.0 -> 1.0.0)

# Set specific version
python scripts/version.py set 1.2.3

The version system maintains consistency across:

  • pyproject.toml (project version)
  • stac_mcp/__init__.py (version)
  • stac_mcp/server.py (server_version in MCP initialization)

Container Development

To develop with containers:

# Build development image
docker build -f Containerfile -t stac-mcp:dev .

# Test the container
docker run --rm -i stac-mcp:dev

# Using docker-compose for development
docker-compose up --build

# For debugging, use an interactive shell (requires modifying Containerfile)
# docker run --rm -it --entrypoint=/bin/sh stac-mcp:dev

Current Containerfile (single-stage) notes:

  • Based on python:3.12-slim for broad wheel compatibility (rasterio, shapely, etc.)
  • Installs GDAL/PROJ system libraries needed by rasterio/odc-stac
  • Installs the package with pip install .
  • Entrypoint: python -m stac_mcp.server (stdio MCP transport)
  • Multi-stage/distroless hardening can be reintroduced later (tracked by potential future ADR)

Documentation

FastMCP Guidelines and Architecture

STAC MCP includes comprehensive documentation for FastMCP patterns and agentic geospatial reasoning:

  • FastMCP Documentation: Complete guide to MCP decorators, resources, tools, and prompts for STAC workflows
    • DECORATORS.md: Choosing the right decorator for STAC operations
    • GUIDELINES.md: FastMCP architecture and usage patterns
    • PROMPTS.md: Agentic STAC search reasoning and methodology
    • RESOURCES.md: STAC catalog discovery and metadata patterns
    • CONTEXT.md: Context usage for logging and progress tracking

These documents provide guidance for:

  • AI agents reasoning about STAC catalog searches
  • Developers implementing STAC MCP features
  • Understanding the planned FastMCP integration (issues #69, #78)

Additional Documentation

STAC Resources

scripts/version.py

#!/usr/bin/env python3
"""Version management script for STAC MCP Server.

This script helps maintain version consistency across:
- pyproject.toml
- stac_mcp/__init__.py
- stac_mcp/server.py

Usage:
    python scripts/version.py current        # Show current version
    python scripts/version.py patch          # Increment patch version (0.1.0 -> 0.1.1)
    python scripts/version.py minor          # Increment minor version (0.1.0 -> 0.2.0)
    python scripts/version.py major          # Increment major version (0.1.0 -> 1.0.0)
    python scripts/version.py set 1.2.3      # Set specific version
"""
import argparse
import re
import sys
from pathlib import Path
from typing import Tuple


def parse_version(version_str: str) -> Tuple[int, int, int]:
    """Parse semantic version string into major, minor, patch."""
    match = re.match(r"^(\d+)\.(\d+)\.(\d+)(?:-.*)?(?:\+.*)?$", version_str)
    if not match:
        msg = f"Invalid semantic version: {version_str}"
        raise ValueError(msg)
    return int(match.group(1)), int(match.group(2)), int(match.group(3))


def format_version(major: int, minor: int, patch: int) -> str:
    """Format version components into semantic version string."""
    return f"{major}.{minor}.{patch}"


def get_current_version() -> str:
    """Get current version from pyproject.toml."""
    pyproject_path = Path("pyproject.toml")
    if not pyproject_path.exists():
        msg = "pyproject.toml not found"
        raise FileNotFoundError(msg)

    content = pyproject_path.read_text()
    match = re.search(r'^version = "([^"]+)"', content, re.MULTILINE)
    if not match:
        msg = "Version not found in pyproject.toml"
        raise ValueError(msg)

    return match.group(1)


def update_pyproject_version(new_version: str) -> None:
    """Update version in pyproject.toml."""
    pyproject_path = Path("pyproject.toml")
    content = pyproject_path.read_text()

    # Update version field
    new_content = re.sub(
        r'^version = "[^"]+"',
        f'version = "{new_version}"',
        content,
        flags=re.MULTILINE,
    )

    pyproject_path.write_text(new_content)
    print(f"Updated pyproject.toml version to {new_version}")


def update_init_version(new_version: str) -> None:
    """Update version in stac_mcp/__init__.py."""
    init_path = Path("stac_mcp/__init__.py")
    content = init_path.read_text()

    # Update __version__ field
    new_content = re.sub(
        r'__version__ = "[^"]+"',
        f'__version__ = "{new_version}"',
        content,
    )

    init_path.write_text(new_content)
    print(f"Updated stac_mcp/__init__.py version to {new_version}")


def update_server_version(new_version: str) -> None:
    """Update version in stac_mcp/server.py."""
    server_path = Path("stac_mcp/server.py")
    content = server_path.read_text()

    # Update server_version in InitializationOptions
    new_content = re.sub(
        r'server_version="[^"]+"',
        f'server_version="{new_version}"',
        content,
    )

    server_path.write_text(new_content)
    print(f"Updated stac_mcp/server.py version to {new_version}")


def update_all_versions(new_version: str) -> None:
    """Update version in all files."""
    update_pyproject_version(new_version)
    update_init_version(new_version)
    update_server_version(new_version)


def increment_version(increment_type: str) -> str:
    """Increment version based on type (major, minor, patch)."""
    current = get_current_version()
    major, minor, patch = parse_version(current)

    if increment_type == "major":
        major += 1
        minor = 0
        patch = 0
    elif increment_type == "minor":
        minor += 1
        patch = 0
    elif increment_type == "patch":
        patch += 1
    else:
        msg = f"Invalid increment type: {increment_type}"
        raise ValueError(msg)

    return format_version(major, minor, patch)


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(description="Manage STAC MCP Server version")
    parser.add_argument(
        "action",
        choices=["current", "patch", "minor", "major", "set"],
        help="Action to perform",
    )
    parser.add_argument(
        "version",
        nargs="?",
        help="Version to set (required for 'set' action)",
    )

    args = parser.parse_args()

    try:
        if args.action == "current":
            current = get_current_version()
            print(f"Current version: {current}")

        elif args.action in ["patch", "minor", "major"]:
            new_version = increment_version(args.action)
            print(f"Incrementing {args.action} version...")
            print(f"Current: {get_current_version()}")
            print(f"New: {new_version}")
            update_all_versions(new_version)
            print("Version updated successfully!")

        elif args.action == "set":
            if not args.version:
                print("Error: Version required for 'set' action", file=sys.stderr)
                sys.exit(1)

            # Validate version format
            parse_version(args.version)

            print(f"Setting version to {args.version}...")
            print(f"Current: {get_current_version()}")
            update_all_versions(args.version)
            print("Version updated successfully!")

    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.