atlasfetch-mcp
OfficialThis MCP server exposes AtlasFetch geospatial tools for reverse geocoding, South African address geocoding, custom boundary/geofence management, and routing.
Reverse geocode coordinates with
lookup_location: country, region, municipality, ISO 3166 codes, optional nearest street, H3 cell/Plus Code, and matches against your boundary sets.Geocode one South African address with
geocode_address: returns a point plus precision/confidence and alternative candidates, useful for delivery dispatch decisions.Manage custom boundary sets with
list_boundary_sets,create_boundary_set,add_boundary, andupdate_boundaryfor delivery zones, service areas, and geofences.Get routes with
get_route: car, bike, foot, or truck; 2–25 locations; turn-by-turn steps; optional route geometry; and avoid or measure exposure against your zones.Use location enrichments such as nearest streets and H3/Plus Code encodings, with boundary-set matches available during lookups and geocoding.
Provides reverse geocoding to administrative boundaries and geofencing using boundary data derived from OpenStreetMap, returning country, region, and municipality with ISO codes.
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., "@atlasfetch-mcpwhich municipality is -33.9249, 18.4241 in?"
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.
atlasfetch-mcp
No signup needed to try it: without a key it falls back to a shared public demo key, capped at 1,000 lookups a month across everyone using it. Get your own limits with a free sign-up at atlasfetch.xyz.
An MCP server for AtlasFetch — reverse geocoding to administrative boundaries and streets, and geofencing, as tools your AI assistant can call directly.
Ask "which municipality is -33.9249, 18.4241 in?" and get Cape Town, Western Cape, South Africa with ISO 3166 codes — from a real point-in-polygon lookup, not the model's memory.
country: United Kingdom (GB) · region: England (GB-ENG) · municipal: City of Westminster (GB-WSM)What it does
Tool | What it does |
| A coordinate becomes the country, region and municipality containing it, with ISO 3166-1 / 3166-2 codes, plus matches from your own boundary sets. Optionally the nearest street (ask for the |
| An address becomes a point, in South Africa today, with a precision that says whether the house, the street or only the suburb was found — and what to do about it. |
| A route between 2 and 25 coordinates by car, bike, foot or truck, with turn-by-turn steps — optionally around your own zones, or measuring how far it runs inside them. |
| Lists your boundary sets, their counts, and which API keys may query them. |
| Creates an empty set for your own polygons. |
| Adds one GeoJSON polygon, with properties returned on every match. |
| Moves or reshapes one boundary, keeping its id, name and properties. |
Good for: reverse geocoding to administrative areas, finding the street a point is on, geofencing against your own delivery zones or service areas, jurisdiction and region checks, tagging location data with region codes, geocoding delivery addresses in South Africa, and routing that keeps out of the areas you name.
Not for: geocoding outside South Africa (yet), searching for places by name, traffic-aware ETAs, distance matrices, isochrones, map tiles, or downloading boundary geometry.
Directions
get_route returns distance, duration and turn-by-turn steps between 2 and 25 coordinates, for car, bike, foot or truck.
car: 3.5 km · 5 min · 11 steps · a location moved 62 m to reach a road — free-flow, no trafficThree things worth knowing before you wire it up:
A route costs 10 lookups, not one. Everything it returns is included in that, but it is ten times a lookup.
Durations are free-flow — the network at its own speed limits, with no live or historical traffic. They are not ETAs. For those, use a traffic-aware service.
The route line is off by default (
routeGeometry: "none"). An encoded polyline is thousands of characters an assistant cannot read; ask forpolyline6orgeojsononly when something will draw it.
Your own zones, in a route. avoidSets names boundary sets to route around; exposureSets names sets to measure against, leaving the route alone and reporting how many metres of it ran inside each zone. A zone containing one of your locations cannot be avoided for that route — it is skipped, and the answer says so.
Plan limits apply: 2 locations on the free plans and up to 25 on Pro, truck is Pro only, and alternatives need exactly 2 locations. Over a cap the answer is a 403 and nothing is billed; a location with no road near it is a 422, and that one is billed, because the route was attempted.
Geocoding
geocode_address turns one address into a point — q on one line, or address already split into parts. South Africa today.
address · confidence 0.96 · 25 Adderley Street, Cape Town, ZA-WC — dispatch
street · confidence 0.47 · ambiguous · Long Street, Cape Town, ZA-WC — ask the customer before dispatchingRead the precision before you use the point. address is the house; interpolated is estimated between the numbers either side; intersection is a street corner ("Cnr Main Rd and Belmont Rd"); site is a named place such as a shopping centre, office park or hospital (pass the shop or unit number on to the driver); street is the right street but not the house; locality is only the suburb. The result line applies the API's own advice for deliveries: dispatch only on a house-, corner- or site-level answer that is not ambiguous, ask the recipient for a pin on a street-level one, and ask the customer when it is ambiguous, listing the other candidates by the suburb each lies in (five Long Streets in Cape Town: City Centre, Observatory, Maitland…). In South Africa most answers are street-level, because OpenStreetMap has house numbers on about 13% of street names.
One address per call, at 1 lookup each, found or not. That is deliberate: a delivery list goes through one row at a time, where you can see what it spends. Programs with thousands of rows should call POST /geocode/batch on the API directly.
Streets
Add the street layer and the answer includes the nearest street, and the house number where one is mapped. It is worldwide, and opt-in — not part of the default country, region, municipal.
You always get three answers — for 5 m, 20 m and unlimited — because "the nearest street" depends on how far you are willing to look:
"street": [
{ "radiusMeters": 5, "streetNumber": null, "streetName": "Adderley Street", "postcode": null, "distanceMeters": 2.7 },
{ "radiusMeters": 20, "streetNumber": "25", "streetName": "Adderley Street", "postcode": "8001", "distanceMeters": 9.6 },
{ "radiusMeters": null, "streetNumber": "25", "streetName": "Adderley Street", "postcode": "8001", "distanceMeters": 9.6 }
]Two nulls that mean different things: "street": null means there is no street data for that country, while an entry whose fields are null means nothing was within that radius. streetNumber and postcode are strings (44A, 12-14, 0181); house numbers are rare outside well-mapped areas. Set streetGeometry and each answer also carries the street's line, trimmed to about 250 m — leave it off unless something will draw it.
Related MCP server: Location MCP Server
Install
Claude Code
claude mcp add atlasfetch -- npx -y atlasfetch-mcpClaude Desktop, Cursor, VS Code and other clients
Add to your MCP config (claude_desktop_config.json, .cursor/mcp.json, or your client's equivalent):
{
"mcpServers": {
"atlasfetch": {
"command": "npx",
"args": ["-y", "atlasfetch-mcp"],
"env": {
"ATLASFETCH_API_KEY": "your-key-here"
}
}
}
}Requires Node 20 or newer.
Getting a key
The demo key is shared by every anonymous user on the internet — one pooled quota and rate limit. Fine for a first look, wrong for anything real.
Create your own at atlasfetch.xyz/dashboard and set ATLASFETCH_API_KEY. The free Personal plan needs no card. You can copy a key from the dashboard at any time — so restrict it to your own domains there. Keys made before 12 October 2026 were revoked; if yours stops working, copy a live one.
Variable | Default | Purpose |
| (demo key) | Your API key. |
|
| Override only for local or staging APIs. |
Two things that surprise people
municipal is the finest unit available, not a consistent kind of thing. Los Angeles returns a city; rural Kansas returns a county; some places return a district. Where a country's fine tier is patchy, a coarser complete tier answers instead, because a county beats null for most uses. Don't assume the value names a city.
A new boundary set cannot be made queryable from here. Sets are created switched off and granted to no key, and granting is addressed by API key id — which only a signed-in browser session can list. So this server can create a set and fill it, but the last step happens in the dashboard. create_boundary_set says so in its response rather than leaving you with a set that silently matches nothing.
Billing
A lookup or a geocoded address is one lookup; a route is 10 on the standard plans. A lookup is one, however many layers, sets or grid codes it touches. Metering happens before matching, so a lookup that matches nothing still counts. Requests rejected as invalid or rate-limited are not billed.
Conventions
A few rules hold across all seven tools, so an input means the same thing wherever it appears.
setnames one set,setsnames several.add_boundaryandupdate_boundaryact on one set (set).lookup_locationandgeocode_addressmatch against any number of sets (sets).get_routeflattens two API objects. The API takesavoid: { sets: [...] }andexposure: { sets: [...] }. The tool takesavoidSetsandexposureSets, because a one-key wrapper object is a step a model gets wrong.…Geometryis always a line you ask to get back, and always off by default.routeGeometryonget_route, andstreetGeometryonlookup_locationandgeocode_address. A plaingeometryinput is a polygon you send, onadd_boundaryandupdate_boundary. (get_routestill acceptsgeometryas an old name forrouteGeometry; it goes in 1.0.)Where a default differs from the API's, it is on purpose:
Tool
Input
This server
API
Why
get_routestepstruefalseTurn-by-turn is what someone asks an assistant for
lookup_locationstreetGeometryadds
streettobase400withoutstreetAsking for a street's line means asking for the street
The first line of every answer is for a person, and is not a contract. It summarises the answer and says what to do with it (
— dispatch,— ask the customer), and its wording will keep improving. The JSON below it is the API's answer, unchanged, and that is what to parse.
Stability
Before 1.0, minor versions (0.3 → 0.4) may rename or remove an input, as 0.4.0 did, though always with a deprecated alias first.
From 1.0:
Frozen: tool names, input names, input types and defaults.
May change in a minor release: new tools, new optional inputs, new values an output can take (as
intersectionandsitewere added toprecision), and the wording of descriptions and of the first line of each answer.Breaking changes go through deprecation. The old form keeps working for at least one minor release, its description says what replaces it, and it is removed only in the next major version.
The JSON in each answer follows the AtlasFetch API, which versions its own contract in its OpenAPI spec.
Every release is described in CHANGELOG.md.
Development
npm install
npm run build
node scripts/smoke.mjs # drives the built server with a real MCP client, against the live APILinks
Playground — try a lookup with no signup
Licence
MIT for this server. Reference boundaries derive from OpenStreetMap under the ODbL; attribution and share-alike obligations pass through to you. Coordinates you look up are not stored.
Available Tools
7 toolsadd_boundaryAdd a polygon to a boundary setADestructive
Adds ONE polygon to an existing boundary set on this account, so that lookup_location reports when a point falls inside it — delivery zones, service areas, sales territories. Each call creates a new boundary: calling twice with the same name stores two.
Before calling: the set must already exist (create_boundary_set). A set matches nothing in lookups until it is switched on and granted to an API key in the dashboard at https://atlasfetch.xyz/dashboard, which this server cannot do.
geometry must be a single GeoJSON Polygon — not a MultiPolygon, Feature or FeatureCollection. Positions are [longitude, latitude]. Each ring needs at least 4 positions and must be closed (last position equals the first); extra rings are holes. Self-intersecting shapes are rejected. To store a MultiPolygon, add each part separately.
A polygon may have at most 50 points on EVERY plan, the closing one included; more is refused, unbilled, with advice to simplify the shape or split it into several boundaries. Plan limits cap properties per boundary and boundaries per set. WITHOUT YOUR OWN KEY, the shared demo key is on the Public plan: properties only category (zone, area, route, place, other), color (green, blue, red, yellow, purple, orange) and priority (low, medium, high), and one set shared with every demo user, where adding to a full set silently deletes its oldest boundary. Never upload anything private with the demo key.
Returns the stored boundary: id, set, name, properties, pointCount and createdAt.
| Name | Required | Description | Default |
|---|---|---|---|
| set | Yes | Name of an existing set on this account (see list_boundary_sets). | |
| name | Yes | Label returned when a looked-up point falls inside this polygon, e.g. "Zone A". | |
| geometry | Yes | A GeoJSON Polygon, e.g. {"type":"Polygon","coordinates":[[[18.40,-33.93],[18.40,-33.90],[18.44,-33.90],[18.44,-33.93],[18.40,-33.93]]]}. | |
| properties | No | Optional flat key/value pairs returned with every match: keys up to 32 characters, values a string (up to 256 characters), number or boolean. The demo key accepts only category, color and priority, from fixed lists. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare destructiveHint=true and idempotentHint=false, and the description explains exactly why: repeated calls with the same name store duplicates, and adding to a full demo set silently deletes the oldest boundary. It also discloses rejection behavior (self-intersecting shapes, >50 points refused unbilled) that the annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and the lookup consequence, then proceeds through prerequisites, geometry rules, plan limits and return shape in a logical order. It is long, but nearly every sentence carries a constraint the agent needs; the demo-key paragraph is the only slightly bloated section.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description names the returned fields (id, set, name, properties, pointCount, createdAt). Combined with prerequisites, geometry limits and failure modes, an agent has everything needed to call it correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents ring closure and [longitude, latitude] ordering. The description nonetheless adds constraints not present in the schema: geometry must be a single Polygon (not MultiPolygon/Feature/FeatureCollection), a hard 50-point cap on every plan including the closing point, and demo-key restrictions on properties values.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Adds ONE polygon to an existing boundary set') and ties it to the downstream effect on lookup_location. It is clearly distinguished from create_boundary_set and update_boundary by the 'each call creates a new boundary' wording.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the prerequisite tool (create_boundary_set), the dashboard step required before lookups match, and the alternative action for MultiPolygons (add each part separately) or oversized shapes (simplify or split). It also scopes demo-key usage with a 'never upload anything private' warning.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_boundary_setCreate a boundary setA
Creates an empty named set to hold your own polygons: delivery zones, service areas, sales territories.
IMPORTANT, and not a bug: a new set is created unavailable and granted to no key, so it matches nothing until both are changed. Granting is addressed by API key id, and nothing this server can call tells it its own key's id. So this tool can create the set and add boundaries to it, but the FIRST grant has to happen in the dashboard: tell the user to switch the set on and grant it to the key this server uses, at https://atlasfetch.xyz/dashboard.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | A short name, used to reference the set in lookups: 1-32 letters, digits, hyphens or underscores, starting with a letter or digit. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses a non-obvious behavioral trait that no annotation conveys: a new set is created unavailable and ungranted, so it matches nothing until both are changed. It explains the root cause (grants are addressed by API key id, which the server cannot discover about itself) and prescribes the exact remediation and dashboard URL. This is consistent with readOnlyHint=false and openWorldHint=true.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in the first clause and the critical caveat follows, with the actionable instruction to relay to the user placed last. It is a bit long, but nearly every sentence carries decision-relevant information; the 'not a bug' framing is slightly defensive but justified given the surprising behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers creation semantics, the grant gap, and the required user follow-up thoroughly for a mutating one-parameter tool. With no output schema, it does not say whether the new set's identifier is returned or how an agent should reference the set in a subsequent add_boundary call, which is the remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single 'name' parameter is fully documented in the schema, including the pattern constraint, so the description adds no syntax or format detail beyond it. The phrase 'named set' only echoes what the schema already says, making baseline 3 correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Creates an empty named set to hold your own polygons') and illustrates the kind of content it holds (delivery zones, service areas, sales territories). This clearly separates it from add_boundary, which populates an existing set.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Implies the create-then-add workflow ('this tool can create the set and add boundaries to it') and gives concrete use cases, so an agent knows when to reach for it. It stops short of explicitly naming an alternative or stating a when-not condition relative to list_boundary_sets or add_boundary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
geocode_addressGeocode an address to a coordinate (South Africa)ARead-only
Turn ONE address into a point, with an honest account of how much of it was found. Built for deliveries. South Africa today; more countries as their data loads. 1 lookup per address, found or not.
Send either q (one line) or address (housenumber, street, suburb, city, postcode, unit) — never both. If the address is already split, send it split: it matches better.
READ precision BEFORE using the point. "address" means the house was found; "interpolated" means it was estimated between the numbers either side; "intersection" means a street corner ("Cnr Main Rd and Belmont Rd", "Bree & Strand Streets"), at the point where the two meet, with the second street in matched.crossStreet; "site" means a named site — a shopping centre, office park, complex or estate, hospital, school, airport or hotel — at a point inside it, named in matched.site; "street" means the right street but NOT the house; "locality" means only the suburb or town. NEVER present a street- or locality-level point as a door: a driver sent there will be at the wrong house. In South Africa most answers are street-level, because OpenStreetMap has house numbers on about 13% of street names — that is the data being honest, not a failure.
What to do with an answer — the result line says which applies:
address, interpolated, intersection or site, not ambiguous, confidence 0.5 or more: dispatch. For a site, pass on the unit or shop number from the original address ("Shop 12") — the answer does not include it, and the driver needs it.
street: ask the recipient for a pin or a landmark.
ambiguous, or locality: ask the customer before dispatching. When the advice is to ask the customer, ASK — do not pick one. result.alternatives holds up to 4 other candidates the answer was chosen from, and the result line lists them with the suburb each lies in and its distance and direction from the first, because they often share a street name AND a town (five "Long Street, Cape Town"s: City Centre, Observatory, Maitland…). Put the first answer and those to the user, by suburb. Free: still one lookup. matched.locality is the place the caller wrote; matched.suburb is the suburb the point actually lies in, absent at locality precision and where no suburb is near. confidence ranks answers; it is NOT a probability. ambiguous means a same-named street elsewhere scored about as well, or the street was a near-miss spelling with no house number to confirm it. result null means nothing trustworthy was found, and that still costs the lookup.
Some addresses name something that cannot be placed: an erf, stand, plot or farm portion (land parcels — there is no cadastre), a township stand, a PO box or private bag. Those parts come back in unmatched and the answer is the place named, at locality: ask the customer for a street address.
country is a hard filter; region (ZA-WC, or "Western Cape") only ranks streets inside it first, so a wrong province does not lose the address. An uncovered country is refused, unbilled, with the list of covered countries in the message — that message is the live coverage list; there is no other. sets and base answer "which delivery zone is it in" in the same call, at no extra cost.
One address per call, deliberately. For a delivery list, call this once per row so the user sees what is being spent; programs with thousands of rows should use the API’s POST /geocode/batch directly. Not for: searching for a business or landmark by name, or a coordinate to an address (that is lookup_location with the street layer).
| Name | Required | Description | Default |
|---|---|---|---|
| q | No | The whole address on one line, e.g. "25 Adderley Street, Cape Town". | |
| base | No | Administrative layers to resolve at the found point. | |
| sets | No | Boundary sets on this account to match at the found point, e.g. delivery zones. | |
| region | No | ISO 3166-2 ("ZA-WC") or a name ("Western Cape"). A preference, not a filter. | |
| address | No | The address already split into parts. Needs at least a street. Use instead of q. | |
| country | No | ISO 3166-1 alpha-2, e.g. "ZA". A hard filter: only that country is searched. | |
| streetGeometry | No | The matched street’s line, trimmed to about 250 m. Only if something will draw it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint; the description carries the real behavioral load: billing (one lookup per address, found or not, null still billed), unbilled refusal for uncovered countries, country as a hard filter vs region as a preference, what confidence and ambiguous actually mean, and that sets/base are free in the same call. Far beyond what the annotations supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded correctly with the one-address-per-call constraint and the precision warning, and nearly every paragraph carries operational information. It is nevertheless very long and repeats itself slightly ("1 lookup per address" vs "One address per call, deliberately", plus the multi-paragraph "Long Street, Cape Town" illustration), which costs it the top mark.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description must describe returns — and it does: precision values, matched.crossStreet, matched.site, matched.locality vs matched.suburb, result.alternatives with suburb/distance/direction, and result null. Combined with the precision-to-action mapping, an agent has everything needed to call this and interpret the response.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, yet the description still adds non-obvious semantics: q vs address are mutually exclusive and pre-split input "matches better", country is a hard filter, region "only ranks streets inside it", and sets/base answer zone questions at no extra cost. It also explains that streetGeometry is trimmed and only worth requesting if something will draw it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope: "Turn ONE address into a point, with an honest account of how much of it was found. Built for deliveries. South Africa today." It also distinguishes itself from the reverse-geocoding sibling at the end ("a coordinate to an address — that is lookup_location"). An agent can tell what this does and what it does not without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use routing per precision level (dispatch / ask for a pin / ask the customer), explicit when-not (business or landmark name search, reverse geocoding, bulk rows which should use the API's batch endpoint), and explicit exclusivity between q and address. This is about as complete a routing guide as a tool description gets.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_routeRoute between coordinates, optionally avoiding your own zonesARead-only
A driving, cycling, walking or truck route from the first location to the last, via any in between, with distance, duration and turn-by-turn steps.
COST: a route is 10 LOOKUPS on the standard plans, not one (a custom plan may charge less; X-Lookups-Remaining is always the truth). Everything it returns is included in that, so do not call it in a loop to compare options without saying so.
Durations are FREE-FLOW: the road network at its own speed limits, with NO live or historical traffic. Never present one as an arrival time in traffic — for that, say a traffic-aware service (Google, Mapbox) is needed.
The route line is NOT returned unless routeGeometry is set, because an encoded polyline is thousands of characters that no one reads. Ask for it only when something will draw it.
Locations are SNAPPED to the nearest routable road, and locations[] in the response says how far each one moved. A large snapDistanceMeters means the answer starts somewhere else than asked — worth repeating to the user.
avoidSets and exposureSets name BOUNDARY SETS on the account (list_boundary_sets), not ad-hoc shapes. avoid routes around every zone in those sets; exposure leaves the route alone and reports how far it ran inside each zone. A zone containing one of the locations cannot be avoided for that route: it is skipped and reported in avoid.skipped. Along very many exposure zones (over 1,000, or over 5 s to measure) exposure is skipped: it comes back empty with a note in errors, and the route is still answered and billed.
TRUCKS (Pro only): a truck over 9 t, or with no weightTonnes, is capped at 80 km/h, as South African law caps goods vehicles; a lighter truck keeps car speeds. The vehicle’s height, width, length, weight, axle load and hazmat are checked against road limits where OpenStreetMap records them, which is not everywhere — a good start, not a legal clearance.
Limits by plan: 2 locations and 1 alternative on the free plans, up to 25 and 2 on Pro; alternatives need exactly 2 locations. Over a cap the answer is 403, and nothing is billed. A location with no road near it, or two places with no road between them, is 422 — and that one IS billed, because the route was attempted.
Not for: distance matrices, isochrones or live traffic (none exist yet), and not for which-area-is-this questions — that is lookup_location, at one lookup.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | Travel mode. Defaults to car. "truck" is Pro only; see TRUCKS above. | |
| steps | No | Turn-by-turn steps per leg. Defaults to true here; set false for distance and time only. | |
| vehicle | No | Truck dimensions and load. Only with mode "truck"; rejected otherwise. Give weightTonnes: without it the truck is treated as heavy (over 9 t) and capped at 80 km/h. | |
| geometry | No | DEPRECATED: use routeGeometry, which takes the same values. Removed in 1.0. | |
| language | No | Language for the step instructions, e.g. "en-GB", "de-DE", "fr-FR". Defaults to en-US. | |
| avoidSets | No | Names of boundary sets on this account to route AROUND. Every zone in them is excluded. | |
| locations | Yes | Origin, any stops in order, then destination. 2 on the free plans, up to 25 on Pro. | |
| alternatives | No | Also return up to this many other routes. Exactly 2 locations only. Plan-capped. | |
| exposureSets | No | Names of boundary sets to MEASURE against: the route is unchanged, and the answer says how many metres of it ran inside each zone. | |
| routeGeometry | No | The route line. Defaults to none. Ask for polyline6 or geojson only when something will draw it: it is long, and an assistant cannot read it usefully. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint/openWorldHint, so the description carries real behavioral weight and does: cost of 10 LOOKUPS with X-Lookups-Remaining as truth, free-flow (no traffic) durations, 403 caps unbilled vs 422 failures billed, snapping with snapDistanceMeters, exposure skipping above 1,000 zones, and truck speed capping. This is exactly the non-obvious context an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded with purpose then COST, and organized into scannable labeled paragraphs. It is long, and a few points (geometry rationale, free-flow warning) are restated across description and schema descriptions, costing some density.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter, nested-object, no-output-schema routing tool, the description covers billing, failure modes, plan caps, snapping, boundary-set semantics, and result contents (distance, duration, steps). An agent could invoke it correctly without reading anything else.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds meaning beyond the schema: avoidSets/exposureSets are confirmed to be account boundary sets resolved via list_boundary_sets, zones containing a location are skipped and reported in avoid.skipped, and missing weightTonnes triggers an 80 km/h cap. That is substantive semantics the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+scope: 'A driving, cycling, walking or truck route from the first location to the last, via any in between, with distance, duration and turn-by-turn steps.' It also draws hard boundaries against siblings ('Not for: distance matrices, isochrones or live traffic ... that is lookup_location').
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use and when-not-to-use: names lookup_location for area questions, warns against looping to compare options, tells the agent to request routeGeometry only when something will draw it, and explains avoidSets vs exposureSets selection criteria. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_boundary_setsList the boundary sets on this accountARead-only
Lists the boundary sets on this account with their boundary counts and the API keys (id and label) each is granted to. A set only matches during a lookup when it is available AND granted to the key making the call. On the shared demo key, grantedKeys is always empty.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuine value beyond that: it discloses the response contents (counts and granted keys per set) and a non-obvious caveat that grantedKeys is always empty on the shared demo key. It stops short of describing pagination, ordering, or error behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the core purpose, followed by only the semantics needed to interpret the output. Every sentence earns its place and there is no repetition of the schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read tool with annotations and no output schema, the description is nearly complete: it covers what is returned and a key edge case. Minor gaps remain around response ordering, empty-state behavior, and pagination, but an agent has enough to call and interpret it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so the baseline is 4 and there is nothing for the description to disambiguate at the input level. The description does explain the meaning of return fields, which is useful because no output schema exists, but parameter semantics themselves are not applicable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Lists the boundary sets on this account') and even enumerates the returned fields (boundary counts, granted API keys). It is clearly distinct from the mutating siblings like create_boundary_set and add_boundary, though it does not explicitly name an alternative tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description supplies interpretive context ('a set only matches during a lookup when it is available AND granted to the key making the call'), which implies why an agent would call it. However, it never explicitly says when to use this tool versus lookup_location or how to act on the results, leaving routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lookup_locationLook up the boundaries containing a coordinateARead-only
Reverse geocode a coordinate to the administrative areas that contain it — country, region (state or province) and municipality — as names plus ISO 3166 codes, optionally the nearest street, and matches from boundary sets the account has uploaded: all in one call.
Use for: which country, region or municipality a point is in; which street a point is on (add "street" to base); geofence checks against your own polygons; tagging data with region codes. For the other direction, an address to a coordinate, use geocode_address. For a route, a travel distance or a travel time, use get_route — 10 lookups on the standard plans to this one’s 1. Do NOT use for place search by name or for fetching boundary geometry.
Caveats worth repeating to the user: municipal is the finest unit AVAILABLE, not a consistent kind of thing — Los Angeles returns a city, rural Kansas returns a county, so do not assume it names a city. A layer that matched nothing comes back null. The errors array is always present: a boundary set that is unavailable or not granted to this key is skipped and reported there, while the call itself still succeeds.
STREETS (opt-in via base, worldwide). base.street is ALWAYS three answers, for 5 m, 20 m and unlimited in that order, each with radiusMeters, streetNumber, streetName, postcode and distanceMeters. A numbered address within the radius wins over a nearer street (for the unlimited entry the address must still be within 20 m); otherwise the nearest named street; unnamed roads never answer. Two different nulls: base.street === null means there is no street data for the point’s country, while an entry whose fields are null means nothing was within that radius — never report them the same way. streetNumber and postcode are STRINGS (44A, 12-14, 0181), house numbers are rare outside well-mapped areas. Street geometry comes back only when streetGeometry is set — leave it off unless something will draw the line.
One call is one billed lookup however many layers, sets or grid codes it touches, and a call that matches nothing still bills.
| Name | Required | Description | Default |
|---|---|---|---|
| lat | Yes | Latitude, -90 to 90. | |
| lng | Yes | Longitude, -180 to 180. | |
| base | No | Which reference layers to resolve. Defaults to country, region and municipal. "street" is opt-in: add it explicitly to get the nearest street. | |
| sets | No | Names of the boundary sets on this account to match against. Defaults to none. | |
| h3res | No | H3 resolution 0-15. 9 is about 400 m across. | |
| encode | No | Also return the point as an H3 cell index and/or Google Plus Code. Output only — neither can be used AS a location, because both name areas rather than points. | |
| pluslen | No | Plus Code length. 9 is not a valid length. 10 is about 14 m. | |
| streetGeometry | No | Give each street answer the line of the street it names, trimmed to about 250 m either side of the point. Implies the street layer. Off by default, and leave it off unless something will draw it: a line is hundreds of characters nobody reads. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint, so the description carries the behavioral burden and does so richly: it discloses billing semantics (one call = one billed lookup regardless of layers, and a no-match call still bills), that the errors array is always present and reports skipped/ungranted boundary sets while the call still succeeds, and the distinction between the two kinds of null for streets. This is far beyond what the annotations supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The definition is front-loaded (purpose, then Use for / alternatives, then caveats, then a dedicated STREETS section) and nearly every sentence carries information that changes agent behavior. It is dense and long, and lines like 'caveats worth repeating to the user' are slightly meta/marketing-toned, but no section is genuinely filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With eight parameters, no output schema, and only two required params, the description compensates thoroughly: it explains the shape of street answers, the always-present errors array, the meaning of each null variant, unit and code formats, and the billing model. An agent has everything needed to call it correctly and interpret the response without a schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds meaning the schema does not: base.street always returns exactly three answers (5 m, 20 m, unlimited) with named fields, streetNumber and postcode are STRINGS with examples, streetGeometry is trimmed to ~250 m and implies the street layer, and 'add street to base' is the opt-in mechanic. Null-vs-null-field semantics for street results are spelled out, which the schema cannot express.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb (reverse geocode) plus the exact resource (administrative areas containing the coordinate) and enumerates the outputs: country, region, municipality with ISO 3166 codes, optional street, plus uploaded boundary-set matches. It also explicitly names the sibling it is not (geocode_address for the opposite direction, get_route for routes), so an agent can disambiguate without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
A dedicated 'Use for:' block lists the concrete tasks (country/region/municipality lookup, street identification, geofence checks against own polygons, tagging with region codes) and names the alternatives with their conditions. It adds explicit exclusions: 'Do NOT use for place search by name or for fetching boundary geometry,' and cites a cost differential versus get_route. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_boundaryMove or reshape a boundary, keeping its name and propertiesADestructiveIdempotent
Replace the shape of ONE existing boundary. Its id, name, properties and set stay as they are, so every lookup that matches it keeps returning the same properties.
Use this to fix or move a zone. Do NOT delete it and add it again: that gives it a new id and loses the properties unless they are re-sent.
Name the boundary by set and name; this finds its id. If a set holds two boundaries with that name, the answer lists their ids and asks for one. The new shape gets the same checks as add_boundary, including the 50-point cap (refused, unbilled). Only boundaries on this account can be changed.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | The boundary’s id (a UUID), when it is known. Use this or name. | |
| set | Yes | The set the boundary is in. | |
| name | No | The boundary’s name. Use this or id. | |
| geometry | Yes | The new shape: a GeoJSON Polygon. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint, idempotentHint and openWorldHint, but the description goes further: it explains what survives the replacement (identity and properties), how ambiguous name resolution behaves (ids returned, caller asked to pick one), the 50-point cap, that a rejected shape is unbilled, and the account boundary. That is exactly the context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Five short sentences, front-loaded with the operation and its invariant (id/name/properties preserved), then the when-to-use, then the error path. No sentence restates the name or title without adding information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation with a nested GeoJSON payload and no output schema, the description covers validation limits, ambiguity handling and preservation semantics. The one gap is what a successful call returns or how to confirm the new shape landed, which the agent has to discover at runtime.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real resolving semantics beyond the schema: naming by set+name performs an id lookup, and duplicate names produce an id list rather than an arbitrary pick. It does not add syntax detail for the geometry rings, which is left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Replace the shape of ONE existing boundary') and immediately scopes it by what is preserved: id, name, properties, set. The capitalization of ONE disambiguates it from bulk or create operations, so an agent can tell it apart from add_boundary without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives the positive case ('Use this to fix or move a zone') and an explicit anti-pattern with the reason ('Do NOT delete it and add it again: that gives it a new id and loses the properties'). It also states the account restriction ('Only boundaries on this account can be changed'), leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.4.1- Added
geocode_address - Added
get_route - Changed
lookup_location1 field changed- added
Input schema / properties / streetGeometryAdded value: +{ + "description": "Give each street answer the line of the street it names, trimmed to about 250 m either side of the point. Implies the street layer. Off by default, and leave it off unless something will draw it: a line is hundreds of characters nobody reads.", + "enum": [ + "polyline6", + "geojson" + ], + "type": "string" +}
- Added
update_boundary
1 tool update
v0.1.6- Changed
lookup_location2 fields changed- changed
Input schema / properties / base / descriptionPrevious value: -"Which reference layers to resolve. Defaults to all three."New value: +"Which reference layers to resolve. Defaults to country, region and municipal. \"street\" is opt-in: add it explicitly to get the nearest street." - changed
Input schema / properties / base / items / enumPrevious value: -[ - "country", - "region", - "municipal" -]New value: +[ + "country", + "region", + "municipal", + "street" +]
2 tool updates
v0.1.4- Changed
add_boundary13 fields changed- removed
Input schema / properties / geometry / additionalPropertiesRemoved value: -{} - changed
Input schema / properties / geometry / descriptionPrevious value: -"GeoJSON geometry, e.g. {\"type\":\"Polygon\",\"coordinates\":[[[lng,lat],...]]}. Longitude comes first. A Feature or FeatureCollection is not accepted."New value: +"A GeoJSON Polygon, e.g. {\"type\":\"Polygon\",\"coordinates\":[[[18.40,-33.93],[18.40,-33.90],[18.44,-33.90],[18.44,-33.93],[18.40,-33.93]]]}." - added
Input schema / properties / geometry / propertiesAdded value: +{ + "coordinates": { + "description": "Rings of [longitude, latitude] positions. The first ring is the outline; any further rings are holes. Each ring is closed: its last position repeats its first.", + "items": { + "items": { + "items": { + "type": "number" + }, + "maxItems": 3, + "minItems": 2, + "type": "array" + }, + "minItems": 4, + "type": "array" + }, + "minItems": 1, + "type": "array" + }, + "type": { + "const": "Polygon", + "type": "string" + } +} - removed
Input schema / properties / geometry / propertyNamesRemoved value: -{ - "type": "string" -} - added
Input schema / properties / geometry / requiredAdded value: +[ + "type", + "coordinates" +] - changed
Input schema / properties / name / descriptionPrevious value: -"A name for this boundary, returned on a match."New value: +"Label returned when a looked-up point falls inside this polygon, e.g. \"Zone A\"." - added
Input schema / properties / properties / additionalProperties / anyOfAdded value: +[ + { + "maxLength": 256, + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } +] - changed
Input schema / properties / properties / descriptionPrevious value: -"Flat key/value pairs returned with every match of this boundary."New value: +"Optional flat key/value pairs returned with every match: keys up to 32 characters, values a string (up to 256 characters), number or boolean. The demo key accepts only category, color and priority, from fixed lists." - added
Input schema / properties / properties / propertyNames / maxLengthAdded value: +32 - changed
Input schema / properties / set / descriptionPrevious value: -"The set to add it to. It must already exist."New value: +"Name of an existing set on this account (see list_boundary_sets)." - removed
Input schema / properties / set / maxLengthRemoved value: -32 - removed
Input schema / properties / set / minLengthRemoved value: -1 - added
Input schema / properties / set / patternAdded value: +"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$"
- Changed
create_boundary_set4 fields changed- changed
Input schema / properties / name / descriptionPrevious value: -"A short name, used to reference the set in lookups."New value: +"A short name, used to reference the set in lookups: 1-32 letters, digits, hyphens or underscores, starting with a letter or digit." - removed
Input schema / properties / name / maxLengthRemoved value: -32 - removed
Input schema / properties / name / minLengthRemoved value: -1 - added
Input schema / properties / name / patternAdded value: +"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$"
4 tool updates
v0.1.2- First observed
add_boundary - First observed
create_boundary_set - First observed
list_boundary_sets - First observed
lookup_location
TDQS
Scored across 7 tools
Each tool targets a distinct action and resource: forward geocode (geocode_address) vs reverse geocode (lookup_location), routing (get_route), and boundary management (create/add/update/list). Descriptions explicitly cross-reference the correct alternative (e.g., 'For the other direction... use geocode_address'), leaving no ambiguity even between inverse geocoding tools.
All tools follow a clean snake_case verb_noun pattern (lookup_location, geocode_address, get_route, add_boundary, create_boundary_set, list_boundary_sets, update_boundary). Multi-word nouns are handled consistently, and no mixed conventions appear.
Seven tools is well-scoped for a geocoding/routing/boundary service: three geospatial query tools and four boundary-management tools. Each earns its place without feeling bloated or thin.
Forward/reverse geocoding and routing are covered, and boundary management has create/add/update/list, but there is no delete_boundary, delete_boundary_set, or get_boundary for a single record. Batch geocoding is also explicitly deferred to the raw API, leaving a notable lifecycle gap that will force workarounds.
Maintenance
Related MCP Connectors
Geocoding, reverse geocoding, and places search for LatLng.
Geocode, reverse-geocode, autocomplete, route and search places.
Geocoding, weather forecasts, and timezone lookups
Resolve coordinates against delivery and coverage polygons (geofences).
Related MCP Servers
- AlicenseCqualityDmaintenanceEnables geocoding and reverse geocoding operations to convert city names to coordinates and coordinates to location names using the API Ninjas service.2MIT
- AlicenseAqualityDmaintenanceEnables IP address location and latitude/longitude address queries, providing detailed geographic information such as country, province, city, and district.210 npmMIT
- AlicenseAqualityDmaintenanceProvides forward/reverse geocoding, bounding box extraction, nearby places discovery, batch geocoding, route waypoints, and administrative boundary lookup using OpenStreetMap data.10Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables forward and reverse geocoding using the OpenCage API, allowing conversion between addresses and coordinates.8 npmMIT