Geo MCP Worker
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., "@Geo MCP Workergeocode the Eiffel Tower"
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.
geo-mcp-worker
English | 简体中文
A stateless MCP server on Cloudflare Workers for geocoding, reverse geocoding, nearby POI search and routing, powered by free OpenStreetMap services (Nominatim / Overpass / OSRM). No API key needed.
Features
Four tools:
geo_geocode,geo_reverse,geo_find_poi,geo_routeFree upstreams, no API key, no KV, no sessions
MCP over Streamable HTTP (JSON responses), protocol versions
2025-06-18,2025-03-26,2024-11-05, JSON-RPC batchesReal walking and cycling profiles (FOSSGIS routers), with car routing via the OSRM demo server
Strict input validation; tool failures come back as structured
{ ok: false, reason }payloads instead of protocol errorsOptional bearer auth (
MCP_AUTH_TOKEN), CORS origin allow-list, per-request upstream timeoutsEvery upstream URL can be pointed at a self-hosted instance
No runtime dependencies;
node:testsuite with mocked upstreams
Related MCP server: OpenStreetMap MCP Server
Quick start
git clone https://github.com/Kerry1020/geo-mcp-worker.git
cd geo-mcp-worker
npm run dev # http://localhost:8787
curl -s http://localhost:8787/healthDeploy to your own account:
npx wrangler login
npx wrangler secret put MCP_AUTH_TOKEN # recommended for public deployments
npm run deployThen add https://<your-worker>.workers.dev/mcp to your MCP client (see MCP client config).
Tools
Tool | Description | Arguments | Source |
| Address text → lat/lon |
| Nominatim (OSM) |
| Lat/lon → address |
| Nominatim (OSM) |
| Nearby POIs, sorted by distance |
| Overpass API (OSM) |
| Point-to-point distance & duration |
| OSRM |
POI categories: restaurant, cafe, school, hospital, clinic, pharmacy, bank, atm, supermarket, convenience, subway, bus_stop, park, gym, cinema, library, kindergarten, police, fire_station, post_office, parking, fuel, marketplace. Any other lowercase [a-z0-9_] value is queried as amenity=<value>.
Coordinates may be numbers or numeric strings. Out-of-range or non-numeric coordinates are rejected with reason: "invalid_coords" (they are not silently clamped).
Endpoints & protocol
POST /mcp— MCP endpoint (JSON-RPC 2.0 over Streamable HTTP, JSON responses only, no SSE)GET /health(also/,/healthz) — health check, always publicOPTIONS *— CORS preflight
Supported methods: initialize, ping, tools/list, tools/call. Notifications (messages without id, e.g. notifications/initialized) are accepted with 202 Accepted and no body. JSON-RPC batches are supported.
Protocol versions: 2025-06-18, 2025-03-26, 2024-11-05 (the client's version is echoed if supported, otherwise 2025-06-18).
Errors
Protocol errors use standard JSON-RPC codes:
Code | When |
| Body is not valid JSON |
| Invalid request (missing |
| Unknown method |
| Unknown tool name, or |
| Unexpected internal error (no stack trace is returned) |
Tool-level failures are not JSON-RPC errors. They come back as a normal result with isError: true and a semantic payload in structuredContent (and as JSON text in content[0].text):
{ "ok": false, "reason": "invalid_coords", "message": "..." }reason values: missing_address, invalid_address, missing_coords, invalid_coords, missing_points, invalid_category, invalid_mode, not_found, upstream_timeout, geocode_error, reverse_error, poi_error, route_error, internal_error.
An empty POI search is a success: { "ok": true, "count": 0, "results": [], "reason": "poi_not_found", ... }, so an agent can retry with a larger radius.
Configuration
All configuration comes from environment variables. Defaults live in wrangler.toml / src/config.js. Nothing is required.
Name | Required | Secret | Default | Description |
| No (recommended) | Yes | unset | If set, every request to |
| No | No |
|
|
| No | No |
| Per-request timeout for upstream APIs, in ms (capped at 60000). |
| No | No |
| Sent upstream. The Nominatim policy asks for an identifying UA, so put your own contact here. |
| No | No |
|
|
| No | No |
| Nominatim base URL. |
| No | No |
| Overpass interpreter URL. |
| No | No |
| OSRM base for |
| No | No |
| OSRM base for |
| No | No |
| OSRM base for |
| No | No |
| Shown in |
The public OSRM demo server (
router.project-osrm.org) only serves the car profile: it gives car routes forfoot/bikerequests too. That is why walking and cycling go to the FOSSGIS routers by default.
For local development, put secrets in .dev.vars (it is git-ignored):
MCP_AUTH_TOKEN=dev-tokenMCP client config
Claude Code (native Streamable HTTP):
claude mcp add --transport http geo https://<your-worker>.workers.dev/mcp
# with MCP_AUTH_TOKEN set on the worker
claude mcp add --transport http geo https://<your-worker>.workers.dev/mcp \
--header "Authorization: Bearer <token>"Claude Desktop, Cursor and other stdio-only clients via mcp-remote:
{
"mcpServers": {
"geo": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://<your-worker>.workers.dev/mcp",
"--header",
"Authorization: Bearer ${AUTH_TOKEN}"
],
"env": {
"AUTH_TOKEN": "<token>"
}
}
}
}Drop the --header arguments and env block if MCP_AUTH_TOKEN is not set.
curl examples
URL=https://<your-worker>.workers.dev # or http://localhost:8787 with `npm run dev`
AUTH="Authorization: Bearer $GEO_MCP_TOKEN" # only needed when MCP_AUTH_TOKEN is set
curl -s $URL/health
curl -s $URL/mcp -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
curl -s $URL/mcp -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
curl -s $URL/mcp -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"geo_geocode","arguments":{"address":"上海市徐汇区云锦路"}}}'
curl -s $URL/mcp -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"geo_find_poi","arguments":{"lat":31.169501,"lon":121.453866,"category":"subway","radius_m":1000,"limit":5}}}'
curl -s $URL/mcp -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"geo_route","arguments":{"from":{"lat":31.169501,"lon":121.453866},"to":{"lat":31.240168,"lon":121.497945},"mode":"driving"}}}'Example results
These were captured from a live deployment. Exact values change as OSM data changes.
geo_geocode
{ "address": "上海市徐汇区云锦路", "limit": 1 }{
"ok": true,
"query": "上海市徐汇区云锦路",
"results": [
{
"lat": 31.169501,
"lon": 121.453866,
"display_name": "云锦路, 龙华, 龙华街道, 徐汇区, 上海市, 200232, 中国",
"type": "residential",
"importance": 0.42
}
]
}School names work too: { "address": "向明中学浦江校区" } → lat=31.075575, lon=121.496283.
⚠ Company and brand names (e.g. "中电金信") are not accepted. For those, use search-mcp first to get the street address, then pass the address to
geo_geocode.
geo_reverse
{ "lat": 31.169501, "lon": 121.453866 }{
"ok": true,
"lat": 31.169501,
"lon": 121.453866,
"display_name": "云锦路, 龙华, 龙华街道, 徐汇区, 上海市, 200232, 中国",
"address": { "road": "云锦路", "suburb": "龙华街道", "city": "徐汇区", "state": "上海市", "postcode": "200232", "country": "中国" }
}geo_find_poi
{ "lat": 31.169501, "lon": 121.453866, "category": "subway", "radius_m": 1000, "limit": 5 }{
"ok": true,
"center": { "lat": 31.169501, "lon": 121.453866 },
"count": 4,
"results": [
{ "name": "云锦路", "distance_m": 0, "category": "subway" },
{ "name": "龙华", "distance_m": 772, "category": "subway" },
{ "name": "龙耀路", "distance_m": 897, "category": "subway" },
{ "name": "龙华", "distance_m": 962, "category": "subway" }
]
}Each result also carries lat, lon and tags (cuisine, opening_hours, phone, website, operator, when available).
geo_route
{ "from": { "lat": 31.169501, "lon": 121.453866 }, "to": { "lat": 31.240168, "lon": 121.497945 }, "mode": "driving" }{ "ok": true, "from": { ... }, "to": { ... }, "distance_m": 12609, "duration_min": 14.7, "mode": "driving", "confidence": "high" }For walking routes longer than 2 km,
duration_minis recalculated at 80 m/min (~4.8 km/h) and flaggedconfidence: "low".
Health
{
"ok": true,
"name": "geo-mcp-worker",
"version": "1.1.0",
"build": { "sha": "<git sha>", "time": "<build time>" },
"tools": ["geo_geocode", "geo_reverse", "geo_find_poi", "geo_route"],
"data_sources": ["nominatim", "overpass", "osrm"]
}Search MCP integration
Geo MCP does spatial computation only. For semantic search, pair it with search-mcp-worker:
User: "What subway stations are near 中电金信 Shanghai HQ?"
1. search_mcp("中电金信上海总部地址") → "上海市徐汇区云锦路XXX号"
2. geo_geocode("上海市徐汇区云锦路") → { lat: 31.17, lon: 121.45 }
3. geo_find_poi(lat, lon, category="subway", radius_m=1000) → 云锦路(0m), 龙华(772m)
4. geo_route(from=office, to=云锦路, mode="walking") → 3 min walkSecurity notes
Without
MCP_AUTH_TOKEN,/mcpis open to anyone who knows the URL, and they can spend your Workers quota and the fair-use budget of the public upstreams. For any public deployment, set a token:npx wrangler secret put MCP_AUTH_TOKEN. The token is compared in constant time./,/health,/healthzandOPTIONSpreflight are always unauthenticated. They return only name, version, build info and the tool list.ALLOWED_ORIGINSlimits browser origins (requests with a non-listedOriginget403). It is not a substitute for the token: non-browser clients can omitOrigin.Request bodies over 1 MB are rejected, and internal errors never return stack traces.
Tool inputs (addresses, coordinates) are sent to the configured upstreams (by default OSM community services). Do not send data you would not share with them.
Development
Requires Node.js ≥ 20. There are no runtime dependencies.
npm test # node:test suite; upstream APIs are mocked, no network needed
npm run check # syntax / import check
npm run dev # local server via wrangler (http://localhost:8787)Layout:
src/index.js Worker entry: routing, CORS, auth, body parsing
src/protocol.js MCP JSON-RPC handling
src/tools.js Tool schemas + handlers (input validation)
src/providers.js Nominatim / Overpass / OSRM clients (timeouts, errors)
src/config.js Env var parsing and defaults
src/http.js Response, CORS and auth helpers
src/util.js Validation and geometry helpers
test/ node:test suites with a mocked global fetchDeploy
npx wrangler login
npx wrangler secret put MCP_AUTH_TOKEN # optional, enables bearer auth
npx wrangler deploy \
--var BUILD_SHA:$(git rev-parse --short HEAD) \
--var BUILD_TIME:$(date -u +%FT%TZ)Use a scoped Cloudflare API token (CLOUDFLARE_API_TOKEN, "Edit Cloudflare Workers" template) for CI or non-interactive deploys. Don't use the Global API Key.
Usage limits of the public upstreams
The default upstreams are free community services with fair-use policies: Nominatim allows at most 1 request/second, and Overpass and the OSRM/FOSSGIS routers throttle heavy users. For production traffic, set MCP_AUTH_TOKEN so the endpoint isn't open to everyone, set an identifying USER_AGENT, and consider self-hosted instances (the *_URL vars).
Expansion roadmap
See docs/GEO_TRANSIT_RESEARCH_REPORT.md for the full survey of 28 projects.
Layer | Solution | Coverage | Status |
Layer 0 | OSRM + Nominatim + Overpass | Global driving/walking/POI | ✓ Deployed |
Layer 1 | Transitous | International public transit | Researched, not deployed |
Layer 2 | Amap (高德) API | China public transit | Researched, not deployed |
Related projects
time-mcp-worker — time zone lookup, conversion and time differences
memory-mcp-worker — persistent KV-backed memory for agents
webhook-inbox-mcp-worker — receive webhooks into KV and read them as MCP tools
summarize-mcp-worker — web page extraction and extractive summarization
image-mcp-worker — image generation via any OpenAI-compatible images API
calc-mcp-worker — math: expressions, calculus, matrices, statistics
search-mcp-worker — multi-engine web search with open, auditable ranking
License
Licensed under the GNU General Public License v3.0.
This server cannot be deployed
Maintenance
Related MCP Connectors
- geoOAuthco.thinair
Geocoding, routing, isochrones, traffic, weather, and place search for AI agents. 19 MCP tools.
MCP server for Japan geodata: cadastral lot numbers (chiban) and reverse geocoding, for AI agents.
Geospatial AI MCP server — satellite imagery, embeddings, weather, GNS governance
Free web search for AI agents. No API key required. Hosted MCP in active development.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceA geospatial MCP server that provides tools for geocoding, routing, elevation profiles, and spatial analysis. It enables AI agents to process GIS file formats like GeoJSON and Shapefiles while performing complex coordinate transformations and distance calculations.4MIT
- 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.30251 npm5MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for geocoding, reverse geocoding, place/POI search, and distance calculation using OpenStreetMap Nominatim, with no API key required.50 npm1MIT
- AlicenseAqualityCmaintenanceMCP server providing structured access to OpenStreetMap data through Overpass and Nominatim APIs, enabling geocoding, nearby place searches, element queries, and tag counting without API keys.7MIT