Deprecated. Deprecated by the publisher: Renamed to io.github.cyanheads/openstreetmap-mcp-server.
Geocode, reverse geocode, and run Overpass spatial queries on OpenStreetMap data.
One address for any MCP client. No sign-in needed on this server.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Odel could not list its tools without signing in.
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).