
Geocode, reverse geocode, and run Overpass spatial queries on OpenStreetMap data.
Geocode, reverse geocode, and run Overpass spatial queries on OpenStreetMap data via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://openstreetmap.caseyjhand.com/mcp
OpenStreetMap data through Nominatim and the Overpass API. Geocode places and addresses, resolve coordinates to the nearest address, look up known OSM objects, and query features by tag around a point, inside a bounding box, or inside an OSM boundary. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
openstreetmap_search_places | Convert a place name or address to coordinates and structured place data |
openstreetmap_reverse_geocode | Convert latitude/longitude to the nearest address or place name |
openstreetmap_lookup_objects | Fetch address records for known OSM objects by ID |
openstreetmap_query_nearby | Find OSM features within a radius of a point |
openstreetmap_query_bbox | Find OSM features inside a bounding box or an OSM boundary such as a city, park, or region |
openstreetmap_query_raw | Run a raw Overpass QL query for anything the other tools cannot express |
openstreetmap_search_places toolquery or the structured fields street, city, county, state, country, postalcode, never both; up to 40 results (limit, default 5), narrowed by countrycodes, layer, featureType, and a viewbox that biases ranking or, with bounded: true, restricts itaddress, boundingbox, importance, and osm_type + osm_id; extratags adds the object's contact and physical-attribute tags; an empty first page fails as no_resultstruncated is confirmed by a same-call probe past limit, and any full page returns nextExcludeIds to pass back as exclude_place_ids; an exhausted walk returns zero results with a notice, not an erroropenstreetmap_reverse_geocode toollat / lon plus zoom from 3 (country) to 18 (building, the default); layer defaults to address,poiaddress, osm_type + osm_id, and boundingbox; coordinates with no OSM data fail as no_coverageopenstreetmap_lookup_objects toolosm_ids per call, each prefixed N, W, or R (["N240109189"]); a malformed ID fails as invalid_id_formatnot_foundopenstreetmap_query_nearby toollat / lon with radius_meters up to 50,000 (default 1,000); one primary tag, amenity or tag_key with an optional tag_value, plus up to five ANDed filters; up to 500 features per pageosm_type + osm_id, coordinates, distance_meters, and the full tag set, sorted nearest-first; effectiveTag echoes the whole filter chainopenstreetmap_query_bbox toolsouth, west, north, east, or within and one R/W boundary ref (R237385) built from a geocoding result's osm_type + osm_id; the same tag filters and 500-per-page cap as openstreetmap_query_nearbyeffectiveArea echoes how within resolved and areasTimestamp how old the Overpass area data is; a ref that maps to no area returns an empty page whose notice names the causewest greater than east is a box crossing the antimeridian; only south greater than north fails, as invalid_bboxopenstreetmap_query_raw tool[out:json], with [timeout:N] injected from timeout_seconds (up to 180, default 30) when the query has none; up to 500 elements per pagenodes[], relations members[]), plus total_elements and the effectiveQuery as sent; malformed QL fails as query_error with the parse error Overpass reportedmax_element_bytes (default 20,000, ceiling 10,000,000) withholds an over-budget element's members, nodes, or geometry arrays whole, listed under withheld_keys; withheldElements and withheldNotice give the offset and budget that fetch it backBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
OpenStreetMap-specific:
OSM_USER_AGENT identifies the server to both upstreamsOSM_OVERPASS_MAX_CONCURRENCY or an entry's |N, with opt-in failover across OSM_OVERPASS_ENDPOINTS (see Overpass endpoint failover)limit and offset and report totalFound, truncated, and nextOffset; a match set of up to 100,000 elements is cached for 10 minutes, so re-paging makes no new upstream request (Nominatim responses are cached for 60), and identical queries in flight at once share one submissionruntime error remark, a Nominatim 400 fails as invalid_parameters naming the rejected parameterOSM_NOMINATIM_BASE_URL and OSM_OVERPASS_BASE_URL point the server at a self-hosted or mirror instanceAgent-friendly output:
effectiveQuery, effectiveTag, and effectiveArea echo what was sent, Overpass responses add data_timestamp and servingEndpoint, and every response carries the ODbL attributionosm_type + osm_id from any tool feed openstreetmap_lookup_objects, and a geocoded relation or way becomes a within ref for openstreetmap_query_bboxtagSelectionCaveat says so (on every openstreetmap_search_places response, and on the other two when extratags is set). Overpass filters take literal text; regex, alternation, and negation go through openstreetmap_query_rawstructuredContent keeps the raw textA public instance is available at https://openstreetmap.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"openstreetmap-mcp-server": {
"type": "streamable-http",
"url": "https://openstreetmap.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"openstreetmap-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/openstreetmap-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"openstreetmap-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/openstreetmap-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"openstreetmap-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/openstreetmap-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
OSM_NOMINATIM_BASE_URL and OSM_OVERPASS_BASE_URL at your own instances.git clone https://github.com/cyanheads/openstreetmap-mcp-server.git
cd openstreetmap-mcp-server
bun install
cp .env.example .env
# edit .env to override endpoints or the User-Agent as needed
| Variable | Description | Default |
|---|---|---|
OSM_NOMINATIM_BASE_URL | Nominatim base URL. A path prefix such as https://maps.example.com/nominatim is supported. | https://nominatim.openstreetmap.org |
OSM_OVERPASS_BASE_URL | Pins every query to one Overpass endpoint and disables failover, for a private instance. Sized by OSM_OVERPASS_MAX_CONCURRENCY. | unset |
OSM_OVERPASS_ENDPOINTS | Comma-separated, ordered Overpass endpoints; more than one enables failover. Append |N to an entry to give it N slots and add capacity (see below). Ignored when OSM_OVERPASS_BASE_URL is set. | https://overpass-api.de/api/interpreter |
OSM_OVERPASS_MAX_CONCURRENCY | Max Overpass queries in flight per endpoint, for the pin and every entry without |N. Match the slot budget the endpoint reports at /api/status. | 2 |
OSM_USER_AGENT | User-Agent sent to Nominatim and Overpass. Required by usage policy. | openstreetmap-mcp-server/<package version> |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. The server declares stateless in code; an explicit value overrides it. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides. An invalid value for any OSM_* variable stops the server at startup with a message naming the variable.
By default every Overpass query goes to one endpoint, the FOSSGIS-operated main instance, so a degraded endpoint fails the call. Listing more in OSM_OVERPASS_ENDPOINTS lets a failure advance to the next entry inside the same call, in list order:
OSM_OVERPASS_ENDPOINTS="https://overpass-api.de/api/interpreter,https://overpass.private.coffee/api/interpreter"
(cooling down), and when every endpoint is cooling a call still tries the one cooling longest.rate_limited for throttling, upstream_error for an instance fault, endpoints_rejected for an HTTP refusal such as a wrong path or a missing key), endpoints_exhausted if none answered, or endpoints_unavailable if none could be reached or they failed in a mix of ways, with each endpoint's outcome in the message.https://overpass-api.de) in servingEndpoint, error messages, and logs. Where an endpoint's own error text quotes an entry's whole path, query string, or userinfo verbatim, the server repeats each as …, so a key in the URL path or query reaches a response or a log only if the endpoint echoes it in another form, such as the key alone or re-encoded. Two entries on one origin read as (entry 1), (entry 2) by list position.[timeout:N].OSM_OVERPASS_MAX_CONCURRENCY at a time. To add capacity, size a mirror with |N — https://mirror.example/api/interpreter|4 gives it four slots of its own, and new calls take the first entry with a free slot. Write a | inside a URL as %7C.pacer_shed and a retryAfter of at least 30 seconds.Adding an endpoint sends your queries to a third party. Before listing one, check its usage policy on the OSM wiki instance list, confirm it holds global data (a regional instance answers a query outside its extract with an empty HTTP 200), and expect mirrors to lag the main instance; servingEndpoint and data_timestamp on every response show which host answered and how fresh its data was.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite
docker run --rm -p 3010:3010 ghcr.io/cyanheads/openstreetmap-mcp-server:latest
# or build locally
docker build -t openstreetmap-mcp-server .
docker run --rm -p 3010:3010 openstreetmap-mcp-server
The image serves HTTP at http://localhost:3010/mcp in stateless session mode and logs to /var/log/openstreetmap-mcp-server. OpenTelemetry peer dependencies are installed by default; build with --build-arg OTEL_ENABLED=false to omit them. Runtime telemetry stays off until you pass -e OTEL_ENABLED=true.
| Directory | Purpose |
|---|---|
src/mcp-server/tools | Tool definitions (*.tool.ts) and shared input, formatting, and escaping helpers. Six tools across Nominatim and Overpass. |
src/services/nominatim | Nominatim service: paced API client for search, reverse, and lookup, plus error parsing. |
src/services/overpass | Overpass service: query builder, endpoint failover and executor, element normalizer, error parsing. |
src/config | Server-specific environment variable parsing and validation with Zod. |
tests/ | Unit and integration tests for tools, services, and security. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arrays in src/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Map data from OpenStreetMap contributors, available under the Open Database License (ODbL).