MCPAI agentsClaudeCursorGeospatial intelligenceOpenStreetMap

Geospatial MCP Server: Give AI Agents Live OpenStreetMap Data

Ask an agent for the nearest pharmacy and it invents a street. Give it live OpenStreetMap and it looks the place up, checks hours, and walks the real network. How the MapLark MCP server does that.

See the MCP serverJoel Sten · Sep 14, 2026Geospatial MCP Server: Give AI Agents Live OpenStreetMap Data

People already talk to Claude, Cursor, and Copilot as if those models can see a city. A guest lands in Stockholm and asks for dinner they can walk to. A parent needs a pharmacy still open at 21:00. A field tech wants EV chargers on this side of the river, not a pin invented from a training run. Those are map questions, and need to be based on reality.

OpenStreetMap already holds the facts those jobs need: where places are, what they are tagged as, when they are open, and how streets actually connect. Plug that planet into the agent and the job changes from sounding plausible to looking it up. That is what the MapLark MCP server does. Paste a config, add a free API key, and ask a map question. The full tool list and per-tier limits live on the product page. The code is open source at github.com/MapLark/osmfeatures-py.

Use it now

No local install. Point Claude, Cursor, or Copilot at https://api.maplark.com/mcp with Authorization: Bearer and your API key. Stdio and HTTP on your machine is still available if you want to manage it yourself. Both paths are on the MCP server page.

json
{
  "mcpServers": {
    "maplark": {
      "url": "https://api.maplark.com/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

Why put OpenStreetMap in an AI agent

An agent without a map is a concierge with an outdated guidebook. It will name a cafe that doesnt exist, a bar that closed last summer, and call a 45-minute detour a 12-minute stroll. The model is not being lazy. It was never given live coordinates, live hours, or a street graph.

OSM is the dataset that already answers those questions for humans, at planet scale, with a tag language that models already know. You want it in the agent whenever the prompt is about the physical world:

  • Travel. Italian restaurants open at 19:30 within a 15-minute walk of the hotel, not a training-set favourite on the other side of town.
  • Night. A pharmacy still open at 21:00, with hours evaluated in that venue's own timezone, not parsed in the model's head.
  • Commute. Whether the office is really a 20-minute walk, on the OSM street network, not as the crow flies across water or rail cuttings.
  • Field work. EV chargers, ATMs, or a supermarket near a job site, ranked by real distance.
  • A night out. A bar crawl that is actually open, ordered on the pedestrian network, then drawn on a map.

The solution is the same in every case. The model chooses what to look for.Lookup names and addresses via geocoding, search OSM tags, walk the roads, and evaluate opening hours.

What goes wrong without a map

Models invent coordinates, because a lat/lng looks like text and text is what they generate. Models estimate distance, and a straight line across a harbour is not a walk. Models try to parse OSM opening_hours strings like Mo-Th 11:00-23:00 in their head, which burns tokens and is not consistent.

Tools fix all of these. The model decides what to ask for. Code computes the answer.

The fix: how MCP solves the glue problem

Before the Model Context Protocol, every agent host invented its own tool-calling glue. An integration written for one chat app had to be rewritten for the next. MCP is a common plug.

MCP roles:

  • Host. The application the user talks to: Claude Desktop, Cursor, Copilot, or your own agent loop. It owns the model and decides which tool calls to make.
  • Client. The connector inside the host. One client per server, managing the session.
  • Server. The process that exposes capabilities. MapLark ships one of these.

Servers expose three kinds of capability: tools (functions the model can call), resources (data the host can read), and prompts (reusable templates). The MapLark server is tool-focused. It has streamable HTTP and stdio.

The handshake is why a good MCP server needs almost no prompt engineering from you. After connection, the server replies with its capabilities, and an instructions string. The host then calls tools/list and receives every tool with a JSON Schema for its arguments. Those schemas go straight into the model's tool-calling context. The model picks a tool and emits arguments, the client sends tools/call, the server runs real code and returns a structured result, and the model reads that result as ground truth rather than inventing one.

That instructions field is where the MapLark server does its most useful work. It ships the planner rules described below, so the model knows to call geocode before searching a named neighbourhood, and knows never to compute a distance itself.

How the MCP server talks to MapLark

You do not want to stand up a new database for every agent, or have it pollute context by reading long docs everytime to figure out how to write raw queries. The server is a thin wrapper over the osmfeatures SDK, which is itself a client for the MapLark HTTP API. The chain is short:

  • Your host talks to https://api.maplark.com/mcp over HTTP, or spawns osmfeatures mcp as a local subprocess.
  • The model calls a tool such as places_search.
  • The server maps that to an HTTP request against https://api.maplark.com, authenticated with your API key (the Authorization header on HTTP, or MAPLARK_API_KEY on stdio).
  • MapLark runs the query against managed PostGIS holding the full OSM planet and returns GeoJSON.
  • The server summarises the result for the model. Coordinate arrays are deliberately left out of the summary so a 200-feature answer does not blow the context window. When the host needs the real geometry to draw it, it calls export_geojson(collection_id).

Some tools never leave your machine. nearest_within, filter_open, point_in_polygon, and points_in_polygon run locally on FeatureCollections the agent already fetched. They cost nothing and add no latency.

This is the same data and the same endpoints as the Places API, Routing API, and OSM Features API. MCP is a second doorway into them, not a separate product.

For privacy, your prompts and conversation stay in the host. We see the HTTP calls the tools make: a bounding box, OSM tags, and a timestamp. See the privacy policy.

Why the tools speak OSM tags

The other design problem is vocabulary. A proprietary category tree means the model must learn your taxonomy. OSM tags are already in the training data. amenity=restaurant, cuisine=italian, shop=supermarket, tourism=hotel, amenity=charging_station, amenity=pharmacy. The model can produce those strings on the first try.

The server exposes 15+ tools in 6 groups: geocode, places, routes, generic OSM queries, local joins that never touch the network, and draw or export. The MCP server page lists every one of them.

Worked example: walking to dinner

Same city, three jobs. The Dinner tab is the walkthrough below. Pharmacy and Commute use the same tools with different tags and a different question.

Dinner Pharmacy Commute
Find Italian restaurants in Sodermalm, Stockholm that are open at 19:30 tonight
and within a 15 minute walk of Mariatorget. Show them on a map.

Send this to Claude or Cursor with the MapLark MCP server connected, and you get:

  1. geocode("Mariatorget, Stockholm") returns a point and a bounding box. The model named a place, so it must resolve it rather than guess coordinates.
  2. routes_isochrone from that point with duration_s=900 and travel_mode=WALK returns the real 15-minute walk polygon. Not a circle. Water, rail cuttings, and missing crossings all shape it.
  3. places_search over a covering bbox with tags=["amenity=restaurant", "cuisine=italian"] and as_of="2026-09-11T19:30:00".
  4. points_in_polygon keeps only the restaurants inside the isochrone.
  5. filter_open keeps the ones where openNow is true at 19:30.
  6. preview_map draws the survivors so you can see them before committing.

One server, one conversation, one prompt. The model is the planner. Code geocodes, walks the street network, evaluates opening hours, and draws the map.

The same pattern, other jobs

Swap the tags and the question. The planner still must not invent metres.

  • Pharmacy at 21:00. Same isochrone from the hotel, then places_search with amenity=pharmacy and as_of at 21:00. filter_open drops anything closed.
  • Is the office a 20-minute walk?geocode both ends, routes_isochrone from the hotel with duration_s=1200, then point_in_polygon for the office. Inside the polygon means yes.
  • EV chargers on this side of the river.places_search with amenity=charging_station, then points_in_polygon against a walk or bike isochrone.
  • A walking bar crawl.places_search for amenity=bar, filter_open, then routes_optimized_path with loop=true so the walk is a tour on the pedestrian network, not a list sorted by name.

Read more on the MCP server page. The restaurant guide example is the same POI pattern as plain Python.

Reservation inventory

The leftover problem after dinner discovery is a table. MapLark answers where things are, what they are tagged as, whether they are open, and how far they are on foot or by bike. It does not hold restaurant inventory. Table availability lives in the restaurant's booking platform.

A well-tagged restaurant often carries website or reservation:website, plus a phone. That is the honest handoff from this server: give the diner the booking page or the number. Live slots need official OpenTable, TheFork, or similar partner APIs, and those companies require a partnership before you get real inventory access. Unofficial scrapers and third-party wrappers exist. They are a workaround to that gate, not something we ship or walk through.

Build the agent yourself

If the problem is "my product has to answer map questions," MCP is the perfect fit. The first tab below connects with the official Python MCP SDK over stdio. load_instructions joins init.instructions from the MCP handshake with your product prompt. Send that string to the model as the system prompt, with list_tools() as the tool schemas. The hardcoded geocode and places_search calls are what the model should emit for the dinner prompt, not metres.

The second tab skips MCP entirely. If you already know the calls, write deterministic code against the osmfeatures SDK. Same data, same endpoints, no protocol overhead.

Custom agent (MCP client) No MCP (direct SDK)
import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

AGENT_INSTRUCTIONS = """
You are a dinner concierge. Prefer walkable places. If the diner wants a
table, hand off the OSM website or phone; do not invent availability.
After a search, call preview_map so they can see the map.
""".strip()


def load_instructions(mcp_instructions: str | None) -> str:
    return "\n\n".join(
        part.strip() for part in (mcp_instructions, AGENT_INSTRUCTIONS) if part
    )


server = StdioServerParameters(
    command="uvx",
    args=["--from", "osmfeatures[mcp]", "osmfeatures", "mcp"],
    env={"MAPLARK_API_KEY": os.environ["MAPLARK_API_KEY"]},
)

async def main() -> None:
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            init = await session.initialize()
            system_prompt = load_instructions(init.instructions)
            tools = await session.list_tools()
            # Your LLM call: system=system_prompt, tools=tools.tools,
            # user="Find Italian restaurants in Sodermalm open at 19:30."
            print(system_prompt)
            print([t.name for t in tools.tools])

            area = await session.call_tool("geocode", {"q": "Sodermalm, Stockholm"})
            hits = await session.call_tool(
                "places_search",
                {
                    "bbox": "18.03,59.30,18.12,59.33",
                    "tags": ["amenity=restaurant", "cuisine=italian"],
                    "as_of": "2026-09-11T19:30:00",
                    "limit": 60,
                },
            )
            print(area.content, hits.content)

asyncio.run(main())

There is a TypeScript SDK too (npm install osmfeatures), and the raw HTTP API is documented in OpenAPI if you would rather work in Go, Rust, or anything else.

Planner versus code: the rule that keeps agents honest

The LLM must never compute a haversine distance, guess which restaurant is nearest a station, parse an opening_hours string, or invent a walk time. Those are the four places agents quietly produce confident nonsense.

The MCP server page maps a dozen example prompts to their tool chains.

The model may chooseCode must compute
Place name, OSM tags (tags with | or !)Coordinates, from geocode or OSM
Viewport bbox, or location plus radiusRanked distances
Distance budget, duration, travel modeNetwork path and isochrone polygon
openNow or asOfWhether a venue is actually open then
Which tool to call nextJoin, filter, sort, slice

Common questions

Is this a Google Places alternative?

For discovery, yes, and usually a much cheaper one. You get OSM tags rather than a proprietary category tree, and no per-session licensing rules about what you may cache. What you do not get is Google's review corpus or business-submitted hours. Data quality tracks OpenStreetMap coverage, which is excellent in cities and patchier in rural areas.

Can the agent book anything on its own?

No. MapLark finds the restaurant, checks hours, and can give the booking URL or phone number. Live table inventory comes from official companies like OpenTable and TheFork, which require partnership signup for real access.

How fresh is the data?

It comes from the OpenStreetMap planet data, refreshed weekly, so an edit a mapper makes today is not available immediately. An agent that looks empty in a rural area is usually telling the truth about the map, not failing the query.

Connect your agent

Grab a free API key, paste it into your MCP config, and ask your first map question.

MCP server docs Get your API key