Skip to main content
Glama
chuk-mcp-geocoder

chuk-mcp-geocoder

by fastmcp-me

Add to Cursor Add to VS Code Add to Claude Add to ChatGPT Add to Codex Add to Gemini

chuk-mcp-geocoder

Geocoding & Place Discovery MCP Server via Nominatim/OpenStreetMap.

Provides forward/reverse geocoding, bounding box extraction, nearby places discovery, batch geocoding, route waypoints, and administrative boundary lookup — designed to work alongside other MCP geospatial servers (DEM, STAC, etc.).

This is a demonstration project provided as-is for learning and testing purposes.

Python 3.11+

Tools

Tool

Description

geocode

Place name to coordinates (lat, lon, bbox, address)

reverse_geocode

Coordinates to place name and address

bbox_from_place

Place name to [west, south, east, north] bbox for DEM/STAC tools

nearby_places

Find places near a coordinate at multiple scales

admin_boundaries

Administrative hierarchy (country, state, county, city, suburb)

batch_geocode

Geocode multiple place names in one call

route_waypoints

Geocode waypoints in order and compute route distances

distance_matrix

Compute haversine distance matrix between multiple points

geocoder_status

Server status and cache statistics

geocoder_capabilities

Full capabilities listing with LLM guidance

Related MCP server: @cyanheads/openstreetmap-mcp-server

Installation

The easiest way to use the server is with uvx, which runs it without installing:

uvx chuk-mcp-geocoder

This automatically downloads and runs the latest version. Perfect for Claude Desktop!

# Install from PyPI
uv pip install chuk-mcp-geocoder

# Or clone and install from source
git clone <repository-url>
cd chuk-mcp-geocoder
uv sync --dev

Using pip (Traditional)

pip install chuk-mcp-geocoder

Usage

With Claude Desktop

Option 1: Use the Public Server (Easiest)

Connect to the hosted public server at chuk-mcp-geocoder.fly.dev:

MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "geocoder": {
      "url": "https://chuk-mcp-geocoder.fly.dev/mcp"
    }
  }
}

Option 2: Streamable HTTP URL (Local)

Run the server locally and connect via HTTP:

{
  "mcpServers": {
    "geocoder": {
      "url": "http://localhost:8010/mcp"
    }
  }
}

Then start the server:

uvx chuk-mcp-geocoder http

Option 3: Run Locally with uvx

{
  "mcpServers": {
    "geocoder": {
      "command": "uvx",
      "args": ["chuk-mcp-geocoder"]
    }
  }
}

Option 4: Run Locally with pip

{
  "mcpServers": {
    "geocoder": {
      "command": "chuk-mcp-geocoder"
    }
  }
}

Standalone

Run the server directly:

# With uvx (recommended - always latest version)
uvx chuk-mcp-geocoder

# With uvx in HTTP mode
uvx chuk-mcp-geocoder http

# Or if installed locally
chuk-mcp-geocoder
chuk-mcp-geocoder http

Or with uv/Python:

# STDIO mode (default, for MCP clients)
uv run chuk-mcp-geocoder
# or: python -m chuk_mcp_geocoder.server

# HTTP mode (for web access and streamable HTTP)
uv run chuk-mcp-geocoder http
# or: python -m chuk_mcp_geocoder.server http

# HTTP mode with custom host/port
uv run chuk-mcp-geocoder http --host 0.0.0.0 --port 9000

STDIO mode is for MCP clients like Claude Desktop and mcp-cli. HTTP mode runs a web server on http://localhost:8010 for HTTP-based MCP clients.

Usage with MCP CLI

uv run mcp-cli --server geocoder,dem,stac

Then ask:

"Get the elevation profile for Mersea Island"

The LLM will use bbox_from_place or geocode to resolve the location, then pass coordinates to the DEM server.

Configuration

Environment Variable

Description

Default

NOMINATIM_EMAIL

Contact email for Nominatim API

(none)

NOMINATIM_BASE_URL

Custom Nominatim instance URL

https://nominatim.openstreetmap.org

MCP_STDIO

Force stdio transport mode

(auto-detect)

Development

Setup

# Clone the repository
git clone <repository-url>
cd chuk-mcp-geocoder

# Install with uv (recommended)
uv sync --dev

# Or with pip
pip install -e ".[dev]"

Running Tests

make test              # Run tests
make test-cov          # Run tests with coverage
make coverage-report   # Show coverage report

Code Quality

make lint      # Run linters
make format    # Auto-format code
make typecheck # Run type checking
make security  # Run security checks
make check     # Run all checks

Building

make build           # Build package
make publish-test    # Upload to TestPyPI for testing
make publish-manual  # Manually upload to PyPI (requires PYPI_TOKEN)
make publish         # Create tag and trigger GitHub Actions release

Architecture

Follows the same 5-layer pattern as chuk-mcp-dem:

  1. core/nominatim.py — Async HTTP client with rate limiting and LRU cache

  2. core/geocoder.py — Async manager with validation and typed dataclass results

  3. models/responses.py — Pydantic v2 response models (extra="forbid", to_text())

  4. constants.py — All configuration, messages, and metadata

  5. tools/*/api.py — MCP tool registration with @mcp.tool() decorators

Public Server

A public instance is hosted at chuk-mcp-geocoder.fly.dev for easy access:

  • URL: https://chuk-mcp-geocoder.fly.dev/mcp

  • Protocol: MCP over HTTPS (Streamable HTTP)

  • Free to use: No API key required

  • Always up-to-date: Running the latest version

Simply add it to your Claude Desktop config:

{
  "mcpServers": {
    "geocoder": {
      "url": "https://chuk-mcp-geocoder.fly.dev/mcp"
    }
  }
}

Data Source

All geocoding data comes from OpenStreetMap via the Nominatim API.

  • Data license: ODbL 1.0

  • API rate limit: 1 request/second (public API)

  • Results are cached in-memory (1 hour TTL)

License

Apache-2.0

Available Tools

10 tools
admin_boundariesA

Get administrative boundary hierarchy for a location.

    Returns the full admin hierarchy from country down to neighbourhood
    for the given coordinates.

    Args:
        lat: Latitude (-90 to 90)
        lon: Longitude (-180 to 180)
        output_mode: "json" (default) or "text"

    Returns:
        Administrative boundaries from largest to smallest
    

Inputs: lat (required), lon (required), output_mode.

batch_geocodeA

Geocode multiple place names in one call.

    Each query is processed sequentially to respect Nominatim rate limits.
    Individual failures don't abort the batch.

    Args:
        queries: JSON array of place names (e.g. '["Boulder, CO", "Denver, CO"]')
        limit: Maximum results per query (default 1)
        output_mode: "json" (default) or "text"

    Returns:
        Per-query results with coordinates, or error for failed queries

    CRITICAL — LLM retry guidance:
        If any query in the batch fails, re-run only the failed queries
        with simplified names using the single geocode tool. Nominatim
        works best with simple place names — remove landmarks, qualifiers,
        and descriptive words before retrying.
    

Inputs: queries (required), limit, output_mode.

bbox_from_placeA

Get a bounding box for a place, suitable for DEM/STAC tools.

    Returns bbox as [west, south, east, north] in EPSG:4326, compatible
    with dem_fetch, stac_search, dem_slope, and other geospatial tools.

    Args:
        query: Place name to get bbox for (e.g. "Palm Jumeirah, Dubai")
        padding: Fractional padding to expand bbox (0.1 = 10% on each side, default 0.0)
        output_mode: "json" (default) or "text"

    Returns:
        Bounding box [west, south, east, north], center point, and approximate area

    CRITICAL — LLM retry workflow when no results are found:
        If the query returns no results, you MUST retry automatically — do
        NOT ask the user. Simplify the query progressively:

        1. Remove specific landmarks/features, keep the broader place.
           "Strood Causeway, Mersea Island" → "Mersea Island"
        2. Drop region/country qualifiers if still no results.
           "Mersea Island, Essex, UK" → "Mersea Island"
        3. Try alternative or official names for the place.
        4. If using a broader place, consider adding padding to cover the
           area of interest (e.g. padding=0.1 for 10% expansion).

        Always retry at least twice before reporting failure.
    

Inputs: query (required), padding, output_mode.

distance_matrixA

Compute haversine distance matrix between multiple points.

    Pure computation — no API calls needed. Accepts points as either
    [lat, lon] pairs or {"name": ..., "lat": ..., "lon": ...} objects.

    Args:
        points: JSON array of points. Each point is either:
                - [lat, lon] pair (auto-named "Point 1", "Point 2", ...)
                - {"name": "Label", "lat": 40.0, "lon": -105.0}
        output_mode: "json" (default) or "text"

    Returns:
        NxN distance matrix in metres between all point pairs
    

Inputs: points (required), output_mode.

geocodeA

Forward geocode a place name to coordinates.

    Searches the OpenStreetMap/Nominatim database for places matching
    the query and returns coordinates, bounding boxes, and address details.

    Args:
        query: Place name or address to search for (e.g. "Boulder, Colorado")
        limit: Maximum results (1-50, default 5)
        countrycodes: Comma-separated ISO 3166-1 country codes to filter (e.g. "us,gb")
        language: Preferred response language (e.g. "en", "de", "fr")
        output_mode: "json" (default) or "text"

    Returns:
        List of matching places with coordinates, bbox, and address details

    CRITICAL — LLM retry workflow when no results are found:
        Nominatim works best with simple, well-known place names. Specific
        or compound names (e.g. "Strood Causeway, Mersea Island, UK") often
        return nothing.

        If the query returns no results, you MUST retry automatically — do
        NOT ask the user. Follow this cascade:

        1. Remove qualifiers and landmarks — keep only the core place name.
           "Strood Causeway, Mersea Island, UK" → "Mersea Island, UK"
           "Portland Harbor, Maine" → "Portland, Maine"
        2. Simplify further — drop region/country qualifiers.
           "Mersea Island, UK" → "Mersea Island"
           "Portland, Maine" → "Portland"
        3. Use countrycodes to narrow broad queries (e.g. countrycodes="gb").
        4. If the place is near a well-known location, geocode that instead
           and report the approximate area.
        5. Try alternative or official names — "The Strood" instead of
           "Strood Causeway", etc.

        Always retry at least twice with progressively simpler terms before
        telling the user the location could not be found.
    

Inputs: query (required), limit, language, output_mode, +1 more.

geocoder_capabilitiesB

Get full server capabilities.

    Returns the complete list of tools, Nominatim API details,
    and LLM-friendly usage guidance.

    Args:
        output_mode: "json" (default) or "text"

    Returns:
        Full server capabilities including tool lists and guidance
    

Inputs: output_mode.

geocoder_statusA

Get geocoder server status.

    Returns server version, Nominatim URL, cache stats, and tool count.

    Args:
        output_mode: "json" (default) or "text"

    Returns:
        Server status information
    

Inputs: output_mode.

nearby_placesA

Find places near a coordinate.

    Discovers nearby places at different scales (buildings, streets,
    suburbs, cities) using reverse geocoding at multiple zoom levels.

    Args:
        lat: Latitude (-90 to 90)
        lon: Longitude (-180 to 180)
        limit: Maximum number of results (default 10)
        categories: Comma-separated OSM categories to filter (e.g. "natural,tourism")
        output_mode: "json" (default) or "text"

    Returns:
        List of nearby places with distances, sorted by proximity
    

Inputs: lat (required), lon (required), limit, categories, +1 more.

reverse_geocodeA

Reverse geocode coordinates to a place name and address.

    Looks up the nearest place for the given coordinates and returns
    the display name, structured address, and bounding box.

    Args:
        lat: Latitude (-90 to 90)
        lon: Longitude (-180 to 180)
        zoom: Detail level 0-18 (18=building, 10=city, 3=country, default 18)
        language: Preferred response language (e.g. "en", "de", "fr")
        output_mode: "json" (default) or "text"

    Returns:
        Place name, address components, and bounding box
    

Inputs: lat (required), lon (required), zoom, language, +1 more.

route_waypointsA

Geocode waypoints in order and compute route distances.

    Resolves each waypoint to coordinates, then computes haversine
    distances between consecutive points.

    Args:
        waypoints: JSON array of place names in route order
                   (e.g. '["Boulder, CO", "Denver, CO", "Aspen, CO"]')
        output_mode: "json" (default) or "text"

    Returns:
        Resolved waypoints, leg distances, total distance, and bounding box

    CRITICAL — LLM retry guidance:
        If a waypoint fails to resolve, simplify its name and retry the
        entire route. Remove landmarks, qualifiers, and descriptive words.
        Use the single geocode tool to test problematic names first.
    

Inputs: waypoints (required), output_mode.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 10 tools

    Disambiguation4/5

    Most tools have distinct purposes, but bbox_from_place and geocode overlap slightly (both take place names) and batch_geocode is a batch version of geocode. Descriptions clarify differences, so agents can likely distinguish them.

    Naming Consistency3/5

    All names use snake_case and are descriptive, but they mix verb phrases (geocode, reverse_geocode), noun phrases (geocoder_status, admin_boundaries), and compound expressions (bbox_from_place, nearby_places). No single consistent pattern.

    Tool Count5/5

    10 tools is appropriate for a geocoding server, covering forward/reverse geocoding, batch operations, bbox extraction, nearby places, admin boundaries, routing, and distance computation without being excessive.

    Completeness4/5

    Covers core geocoding operations well. Minor gaps: no explicit search by category or type, but nearby_places and admin_boundaries provide related functionality. The retry workflows in descriptions enhance completeness.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      F
      maintenance
      An MCP server providing geocoding and place discovery services via Nominatim and OpenStreetMap. It enables users to perform forward and reverse geocoding, extract bounding boxes, and find nearby places or administrative hierarchies.
      10
      Apache 2.0
    • A
      license
      A
      quality
      C
      maintenance
      Enables LLMs to geocode and reverse geocode places, find nearby points of interest, search categories within an area, get turn-by-turn routing, suggest meeting points, and analyze neighborhoods, commutes, schools, EV charging, and parking through OpenStreetMap data services. It also serves place and map tile resources over stdio, SSE, or Streamable HTTP transports.
      12
      MIT