Open Reach
Provides geospatial reachability and catchment-scoring tools for out-of-home (OOH) site evaluation, using OpenStreetMap data for geocoding, POI queries, and isochrone generation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Open Reach Rank 3 billboard sites near KLCC, Bukit Bintang, Mont Kiara by 10-min walk catchment."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
Related MCP server: @nor-data/nabolag-mcp
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 |
| Forward geocode a place name. | lat/lon decimal degrees |
| Coordinate → address string. | decimal degrees → string |
| Reachable-area polygon within a time budget. | minutes; polygon |
| POIs in the catchment, grouped by enum category. | counts |
| Composite reach score | unitless |
| 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_mcpClaude 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-missingMethodology & 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)wheredis its distance from the site andDa 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-Agentheader (configurable; required by Nominatim).transitmode falls back to the OSRMfootprofile — OSRM has no transit router. For real transit isochrones, self-host a transit router (e.g. RAPTOR/OTP) and pointOSRM_BASE_URLat 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_URLenv 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 |
|
| Geocoding upstream |
|
| POI upstream |
|
| Routing upstream |
|
| Required by Nominatim policy |
|
| TTL cache sizing |
|
| Per-host request spacing |
|
| Upstream call timeout |
Publishing
v1 ships stdio transport. Publishing steps (run manually after build):
PyPI:
python -m build && twine upload dist/*.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.
Available Tools
7 toolscatchment_poisA
Points of interest inside a site's catchment, grouped by category.
Units: lat/lon decimal degrees; minutes minutes. categories is
enum-constrained (no free-text queries).
Example: catchment_pois(lat=3.15, lon=101.71, minutes=10, mode="walk",
categories=["cafe","retail"])
Inputs: lat (required), lon (required), mode (required), minutes (required), +1 more. Returns: POIs inside a site's catchment, grouped by category.
compare_sitesA
Rank candidate sites by reach score (server-side, deterministic).
Ranking is computed here, not by the client model. Tie-break chain:
score desc, POI density desc, category diversity desc, name asc.
Units: lat/lon decimal degrees; minutes minutes.
Example: compare_sites(sites=[{"name":"KLCC","lat":3.16,"lon":101.71},
{"name":"Bukit Bintang","lat":3.15,"lon":101.72}],
minutes=10, mode="walk")
Inputs: mode (required), sites (required), minutes (required), weights, +1 more. Returns: Ranked result of comparing candidate sites.
geocodeA
Forward geocode a place name to a WGS84 coordinate (decimal degrees).
Units: latitude/longitude in decimal degrees. Example: geocode(query="KLCC") -> GeoPoint(lat=3.1579, lon=101.7116)
Inputs: query (required). Returns: A WGS84 coordinate.
isochroneA
Reachable-area polygon within a travel-time budget.
Units: lat/lon in decimal degrees; minutes in minutes; polygon ring
as [lon, lat] pairs; area_m2 in square metres.
Example: isochrone(lat=3.15, lon=101.71, mode="walk", minutes=10)
Inputs: lat (required), lon (required), mode (required), minutes (required). Returns: Reachable area polygon within a time budget. ``polygon`` is a list of ``[lon, l…
pingA
Health-check tool.
Returns {"status": "ok"} so any MCP client can confirm reachability.
Example: ping() -> {"status": "ok"}
Inputs: no named parameters. Returns: object
reverse_geocodeA
Reverse geocode a coordinate to a human-readable address.
Units: lat/lon in decimal degrees; returns a display-name string. Example: reverse_geocode(lat=3.15, lon=101.71) -> "Bukit Bintang, ..."
Inputs: lat (required), lon (required). Returns: result
site_reach_scoreA
Composite reach score in [0, 1] for a single OOH site.
Combines gravity-weighted POI density, junction density, and category
diversity (public urban-analytics methodology). Returns the score plus an
auditable breakdown.
Units: lat/lon decimal degrees; minutes minutes; score unitless [0,1].
Example: site_reach_score(lat=3.15, lon=101.71, minutes=10, mode="walk")
Inputs: lat (required), lon (required), mode (required), minutes (required), +2 more. Returns: Composite reach score for a single site. ``score`` is in ``[0, 1]``; ``breakdow…
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 7 tools
Each tool serves a distinct purpose: health check, forward/reverse geocoding, isochrone generation, POI query, and reach scoring/comparison. No two tools overlap in function, minimizing agent confusion.
Most tools follow a verb_noun or noun_phrase pattern (geocode, reverse_geocode, compare_sites). However, 'isochrone' and 'catchment_pois' are noun-oriented rather than verb-first, introducing slight inconsistency but still clear.
With 7 tools, the server covers a focused domain (location reachability analysis) without bloat. Each tool feels necessary, and the count is well within the 3–15 sweet spot.
The set provides a complete workflow: geocode, reverse geocode, compute isochrone, query POIs, score a site, and compare multiple sites. A minor gap is the lack of a tool to fetch POI details or handle transit-specific parameters, but core needs are met.
Maintenance
Related MCP Connectors
MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.
Unified 15-tool SEO, SERP, clean Markdown & GEO (LLM citation Share-of-Voice) MCP server.
Geospatial AI MCP server — satellite imagery, embeddings, weather, GNS governance
Canonical British Columbia Property Intelligence & Risk Screening MCP Server.
Related MCP Servers
- AlicenseBqualityDmaintenanceA comprehensive MCP server providing 30 tools for geocoding, routing, and OpenStreetMap data analysis. It enables AI assistants to search for locations, calculate travel routes, and perform quality assurance checks on map data.30941 npm6MIT
- AlicenseAqualityDmaintenanceMCP server that wraps open Norwegian neighborhood data, enabling queries for public transport coverage, traffic noise, and green areas around a location.358 npmMIT
- AlicenseCqualityCmaintenanceMCP server for the UK Bus Open Data Service, enabling timetable queries, stop search, route discovery, journey planning, and real-time bus tracking.1622 PyPIMIT
- FlicenseNot gradedqualityBmaintenanceAn MCP server providing geocoding, routing, isochrones, and POI-by-proximity search through self-hosted OpenStreetMap backends.-