Open Reach

Open Reach

An MCP server for out-of-home (OOH) site-reachability tooling that provides geospatial catchment scoring and multi-site comparison for billboard/ad-site evaluation using open data.

Category
Visit Server

README

Open Reach

An MCP server for out-of-home (OOH) site-reachability tooling.

Open Reach exposes geospatial reachability and catchment-scoring tools for billboard / ad-site evaluation — isochrone catchments, POI-density scoring, and multi-site comparison — to any MCP client (Claude Desktop, Cursor, etc.), built entirely on open geospatial data and public urban-analytics methodology.

The differentiator is server-side reach scoring and multi-site ranking (site_reach_score, compare_sites). There are several public OSM-wrapper MCP servers already; none of them do catchment scoring. That gap — plus honest, public methodology — is the point of this project.


The hook (demo)

You (in Claude Desktop):
  "I'm scouting 3 candidate billboard sites near KLCC, Bukit Bintang, and
   Mont Kiara. Which has the best pedestrian catchment and highest POI
   density within a 10-minute walk?"

Claude:
  → geocode("KLCC"), geocode("Bukit Bintang"), geocode("Mont Kiara")
  → isochrone(..., mode="walk", minutes=10)            [x3]
  → catchment_pois(..., categories=["cafe","retail","transit"])  [x3]
  → site_reach_score(...)                               [x3]
  → compare_sites(sites=[...], minutes=10, mode="walk")
  ← ranked: 1. Bukit Bintang  2. KLCC  3. Mont Kiara  (with per-site breakdown)

The ranking arithmetic runs server-side in compare_sites — the model's job is orchestration and narration, not the math.


How it works

  ┌─────────────────────┐   MCP (JSON-RPC over stdio)
  │   MCP Client        │ ──────────────────────────────┐
  │ (Claude Desktop /    │                               │
  │  Cursor / Code)      │ ◄── tool schemas advertised  │
  └─────────────────────┘                               ▼
                                ┌────────────────────────────────────┐
                                │       Open Reach MCP Server         │
                                │          (FastMCP process)          │
                                │                                      │
                                │  @mcp.tool: geocode                  │
                                │  @mcp.tool: reverse_geocode          │
                                │  @mcp.tool: isochrone                │
                                │  @mcp.tool: catchment_pois           │
                                │  @mcp.tool: site_reach_score  ★      │
                                │  @mcp.tool: compare_sites     ★      │
                                │                                      │
                                │  TTL cache + per-host rate limiting  │
                                └──────────────────┬───────────────────┘
                                                   │ https GET/POST
                ┌──────────────────────────────────┼───────────────────────┐
                ▼                                  ▼                       ▼
      ┌───────────────────┐         ┌──────────────────────┐   ┌───────────────────┐
      │ OSM Nominatim     │         │ Overpass API         │   │ OSRM              │
      │ (geocoding)       │         │ (POIs by category)   │   │ (isochrones)      │
      └───────────────────┘         └──────────────────────┘   └───────────────────┘

Each scoring tool chains isochrone → POIs/junctions → a pure, unit-tested arithmetic module, then returns a typed result plus an auditable breakdown.


Tools

Tool Description Units
geocode(query) Forward geocode a place name. lat/lon decimal degrees
reverse_geocode(lat, lon) Coordinate → address string. decimal degrees → string
isochrone(lat, lon, mode, minutes) Reachable-area polygon within a time budget. minutes; polygon [lon,lat]; area m²
catchment_pois(lat, lon, minutes, mode, categories) POIs in the catchment, grouped by enum category. counts
site_reach_score(lat, lon, minutes, mode, weights?) Composite reach score [0,1] + breakdown. ★ unitless [0,1]
compare_sites(sites, minutes, mode, weights?) Deterministic multi-site ranking. ★ ranks (1 = best)

mode{walk, drive, transit}. categories are enum-constrained — callers never supply raw Overpass QL; all queries are built server-side.


Quick start

git clone https://github.com/kasturi/open-reach-mcp.git
cd open-reach-mcp
python -m venv .venv && .venv\Scripts\activate     # Windows
# source .venv/bin/activate                        # macOS/Linux
pip install -e ".[dev]"

Run standalone (stdio):

open-reach-mcp            # or: python -m open_reach_mcp

Claude Desktop config

Add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "open-reach": {
      "command": "C:\\path\\to\\open-reach-mcp\\.venv\\Scripts\\open-reach-mcp.exe",
      "args": []
    }
  }
}

Set USER_AGENT (see .env.example) to a descriptive value — Nominatim usage policy requires it.

Tests

pytest --cov=open_reach_mcp --cov-report=term-missing

Methodology & IP boundary (important)

The reach-scoring formula in site_reach_score is derived only from public urban-analytics literature, not from any proprietary methodology or real campaign/device data:

  • Gravity-model catchment accessibility (Hansen, 1959; classic spatial interaction) — each POI contributes exp(-d / D) where d is its distance from the site and D a pedestrian decay constant.
  • Walkability-style density indices — POI density and road-junction density per km², normalized against public reference benchmarks to [0, 1].
  • Category-mix diversity via normalized Shannon entropy.

This project stands for the engineering pattern (geospatial proxy scoring under rate-limited free APIs, packaged as an MCP server). It is not a claim of parity with any production reach/audience system, and it uses no proprietary formulas or real client data.


Upstream dependencies & limits

All upstream APIs are free-tier and shared/public, so they are rate-limited and occasionally fragile. Open Reach mitigates this with:

  • TTL cache (CACHE_TTL_SECONDS, default 24h) + per-host rate limiting (RATE_LIMIT_MIN_INTERVAL_SECONDS, default 1s — Nominatim's policy ceiling).
  • A compliant User-Agent header (configurable; required by Nominatim).
  • transit mode falls back to the OSRM foot profile — OSRM has no transit router. For real transit isochrones, self-host a transit router (e.g. RAPTOR/OTP) and point OSRM_BASE_URL at it. This is a known limitation, stated openly rather than hidden.
  • For production throughput, self-host Nominatim / Overpass / OSRM (Docker images exist for all three) and set the *_BASE_URL env vars. Turning upstream fragility into a documented self-host path is part of the point.

Configuration

All settings are environment-driven (see .env.example):

Variable Default Purpose
NOMINATIM_BASE_URL https://nominatim.openstreetmap.org Geocoding upstream
OVERPASS_BASE_URL https://overpass-api.de POI upstream
OSRM_BASE_URL https://router.project-osrm.org Routing upstream
USER_AGENT open-reach-mcp/0.1.0 (...) Required by Nominatim policy
CACHE_MAXSIZE / CACHE_TTL_SECONDS 2048 / 86400 TTL cache sizing
RATE_LIMIT_MIN_INTERVAL_SECONDS 1.0 Per-host request spacing
HTTP_TIMEOUT_SECONDS 15.0 Upstream call timeout

Publishing

v1 ships stdio transport. Publishing steps (run manually after build):

  1. PyPI: python -m build && twine upload dist/*.
  2. Official MCP registry: submit server.json (verified GitHub ownership) at registry.modelcontextprotocol.io. PyPI alone no longer supports a "published to the registry" claim; the official registry (launched Sept 2025) requires the manifest + verified ownership.

SSE-only transports are deprecated since MCP spec 2025-03-26. A Streamable HTTP transport is the planned v2 stretch (no SSE).


License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured