PlaceRoot
PYPI · PLACEROOT · 2 COMPONENTS · SCANNED SEP 20
Grounds AI agents in Overture Maps open data — compact answers, no API key.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →
Supply Chain Security50
- Malware scan not yet available for this package.Unverified
- No known CVEs affecting this package version or its production dependencies.Pass
- Runs hatchling.build at install time, a recognised native-build step with no shell scripting around it. View diagnostics → Pass
- 1 of 30 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency35
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Provenance check failed: no build-provenance attestation is published. See how to fix → View diagnostics → Fail
- License check failed: no license is declared. See how to fix → Fail
- Actively maintained (last published 24 days ago).Pass
- Publishes a security disclosure policy (SECURITY.md).Pass
Schema Quality & AI Usability68
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (good).Pass
- Context-footprint check failed: tool/resource definitions use about 21714 tokens (~482/item across 45 items; 42 tools + 3 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management97
- Stability observed for 29 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage74
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 10% of tool parameters carry a description.Partial
- Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Tool Safety100
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- We read all 42 captured tool definition(s), and no name or description among them implies an irreversible operation.Pass
- An AI judge read all 44 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a current MCP spec version (2026-07-28).Pass
How do I install the PlaceRoot MCP server?
PlaceRoot runs locally as a PyPI package, launched with uvx placeroot. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
pypi · placeroot
claude mcp add chuofringer-placeroot -- uvx placeroot
{
"mcpServers": {
"chuofringer-placeroot": {
"command": "uvx",
"args": [
"placeroot"
]
}
}
} {
"servers": {
"chuofringer-placeroot": {
"command": "uvx",
"args": [
"placeroot"
]
}
}
} codex mcp add chuofringer-placeroot -- uvx placeroot
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"chuofringer-placeroot": {
"type": "local",
"command": [
"uvx",
"placeroot"
],
"enabled": true
}
}
} openclaw mcp add chuofringer-placeroot --command uvx --arg placeroot
mcp_servers:
chuofringer-placeroot:
command: "uvx"
args: ["placeroot"] {
"McpServers": {
"chuofringer-placeroot": {
"Transport": "stdio",
"Command": "uvx",
"Arguments": [
"placeroot"
]
}
}
} assistant mcp add chuofringer-placeroot -t stdio -c uvx -a placeroot
{
"mcpServers": {
"chuofringer-placeroot": {
"command": "uvx",
"args": [
"placeroot"
]
}
}
} Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 20 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 93 to 97. That category is still filling its 30-day observation window: 28 days of observed history at the previous scan, 29 at this one. The score rises as the window fills, whether or not the server changes.
- 18 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 87 to 90. That category is still filling its 30-day observation window: 26 days of observed history at the previous scan, 27 at this one. The score rises as the window fills, whether or not the server changes.
- 16 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 80 to 83. That category is still filling its 30-day observation window: 24 days of observed history at the previous scan, 25 at this one. The score rises as the window fills, whether or not the server changes.
- 15 Sept 26 −3
- Stability: pass → 0.80 functional
- 13 Sept 26 −15
- Malware scan: pass → unverified ▼ security
- Stability: 0.97 → pass security
- 12 Sept 26 +16
- Malware scan: unverified → pass ▲ security
- 10 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 87 to 90. That category is still filling its 30-day observation window: 26 days of observed history at the previous scan, 27 at this one. The score rises as the window fills, whether or not the server changes.
- 8 Sept 26 −14
- Malware scan: pass → unverified ▼ security
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 20 Sept 2026 · Analysed pypi/placeroot@0.10.0
Provenance No attestation
The registry publishes no build provenance for this version, so there is nothing to verify.
| Result | No attestation |
|---|---|
| Ecosystem | pypi |
Background: How many MCP packages publish verified provenance →
Install scripts 1 script
| Hook | Tier | Command |
|---|---|---|
| build_backend | allowlisted | hatchling.build |
Background: Why install scripts are a supply-chain risk →
Dependencies 30 packages
| Packages resolved | 30 |
|---|---|
| No linked repository | 1 |
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
address_at Nearest street addresses to a point ~391
Nearest street addresses to a point, nearest first: number, street, unit, postcode. The address-level counterpart to reverse_geocode (issue #188): where that returns one collapsed hop plus the admin chain, this returns the few doorways around the point with the attributes an address lookup wants. Returns {"results": [{number, street, unit, postcode, postal_city, address_levels, country, distance_m}, ...]}, capped at 5. Optional attributes are omitted when the source has no value for them. No id is returned: Overture documents address ids as not GERS-stable, so unlike a place/division/building id there is no durable handle to hand out. For a stable reference to what is at a coordinate, use reverse_geocode and hold onto the division it names. Coverage is the thing to read carefully. The addresses theme is Overture's only alpha theme and covers 39 countries — no UK, Ireland, India, China, Korea or Russia, no Africa or Middle East, and little of Latin America outside Brazil, Mexico, Chile, Colombia and Uruguay. An empty results list is a valid answer, never an error, and always carries a "note" saying whether the country is outside the theme's coverage entirely or is covered but had nothing within the search radius. That country is the one whose division polygon contains the point, so the note stays correct next to a border; if the lookup behind it cannot run, the note says so rather than asserting anything about the data. Returns a structured {"error": ...} if upstream is unavailable, if the dataset is missing the bbox/street columns this depends on, or for an out-of-range coordinate.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| limit | integer | – | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
admin_lookup Admin hierarchy lookup ~146
Containing admin hierarchy for a point: neighborhood up to country. Point-in-polygon against Overture's divisions theme. Returns {"chain": [{"name": ..., "type": "locality", "id": ...}, ...]} smallest division first (e.g. neighborhood, then locality, county, region, country) — an empty chain means no division in the active dataset contains the point, which is a valid answer for remote areas, not an error. Returns a structured {"error": ...} if upstream is unavailable or the divisions dataset is missing the geometry column this tool depends on.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
buildings_at Buildings near a point ~180
Nearest building footprints to a point, nearest first. From Overture's buildings theme (issue #23). Returns {"results": [{id (GERS), subtype, class, footprint_area_m2, height_m, num_floors, distance_m}, ...]}. No raw geometry by default (design rule: answers, not data) — pass include_geometry=true to also get each row's footprint as GeoJSON, simplified to a small per-row token cap (each row then also carries geometry_max_deviation_m, reporting what was lost). Returns a structured {"error": ...} if upstream is unavailable or the dataset is missing geometry/bbox.
| Name | Type | Req | Description |
|---|---|---|---|
| include_geometry | boolean | – | – |
| lat | number | yes | – |
| limit | integer | – | – |
| lon | number | yes | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
changes_in_area Changes in area ~1,065
What's opened or closed around here since a past Overture release. Use this for "what's new around here", "what's closed since spring", or any question with a time dimension — every other tool here answers against a single, current snapshot of the data; this is the only tool that compares two. Area, exactly one of: - place: a free-text area name ("Palo Alto"), resolved with this server's usual free-text area matching (prominence-ranked, "City, ST" suffix aware). The resolved division is echoed back as "area" on the response. A name matching several equally-ranked divisions returns {"error": "ambiguous_area", "candidates": [...]} rather than silently picking one; an unresolvable name returns {"error": "not_found"}. If the resolved division is too large to diff (bigger than this tool's per-side degree cap — countries, large regions), returns a {"error": "bad_request"} naming the area and suggesting a smaller one (a neighborhood or district instead of a whole city/region) or an explicit bbox. - min_lon/min_lat/max_lon/max_lat: an explicit bbox (all four together, or none) — for when the caller already has coordinates rather than a name. Same size cap as the named-area path. category, when given, filters both releases' scans identically before diffing — see diff_places; a place that changed OUT of the category between releases reads as "disappeared" from this filtered view, which is the correct reading of "restaurants that changed", not a bug. from_release/to_release: Overture release strings (YYYY-MM-DD.N). Omit both to diff the previous release against the ACTIVE one — to_release defaults to release.resolve_release(), the same release every other tool in this conversation queries (env pins included), and from_release defaults to the newest listed release older than it: an adjacent, recent window. Not the oldest release Overture still serves — years-old releases are schema-drifted enough that most compared columns NULL out and everything…
| Name | Type | Req | Description |
|---|---|---|---|
| category | – | – | – |
| from_release | – | – | – |
| limit | integer | – | – |
| max_lat | – | – | – |
| max_lon | – | – | – |
| min_lat | – | – | – |
| min_lon | – | – | – |
| place | – | – | – |
| to_release | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
compare_areas Compare areas ~834
Compare 2-5 areas side by side: category mix, density, and what differs. areas is a list of centers sharing one radius_m, each a {"lat": ..., "lon": ...} dict, a GERS id, or a free-text place/area name, mixed freely — a named area compares the same radius_m circle around its resolved point as a coordinate would (not its actual boundary; that's a later feature). An id/name that failed to resolve returns an indexed error (areas[i]: ...) with candidates on ambiguity. Any area given by id/name adds "resolved": [{"index": i, "name", "id", "lat", "lon", "matched_by"}, ...] for just those areas; absent when every area was already coordinates. Returns per-area total_places, place density per km^2, and category_counts aligned across areas for the top ~10 categories by combined count, plus "differentiators" — those categories ranked by how much they differ, relatively, between areas (the fastest way to answer "how is area A different from area B"). Returns a structured {"error": ...} if areas isn't 2-5 centers, or if upstream is unavailable or the dataset is missing columns this tool depends on for any area (a partial comparison is not returned). priorities (optional, up to 6) turns the comparison into a scored verdict: each entry is {"label": your own term for the criterion, e.g. "competition"; "category": an Overture taxonomy slug, or "__density__" for overall place density as a foot-traffic proxy; "prefer": "more" | "fewer"; "weight": 0.1-5, default 1}. Each area's raw measure per priority is that category's count (or density) within radius_m — matched exactly against the category taxonomy (slug plus its descendants, so "park" never counts parking garages) and counted explicitly even for categories outside the top-10 alignment above; the per-priority winner is whichever area is better on that raw measure (a tie has no winner for that priority); each area's verdict score is the weight-summed share of each priority normalized against the best area (measure/max for "more",…
| Name | Type | Req | Description |
|---|---|---|---|
| areas | array | yes | – |
| priorities | – | – | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
data_version Data version ~139
Which Overture Maps release backs the answers from every other tool. Reports the active release string, its date, and whether it came from live S3 discovery, an operator env override, or the pinned fallback baked into this build. Resolved once at process start and cached for the process lifetime — this tool just reports that cached value, it doesn't re-check upstream, so it's small and has no upstream DB dependency. The body is resources.data_version_payload(), shared verbatim with the placeroot://data-version MCP resource so the two surfaces cannot drift (issue #195); tests/test_resources.py asserts they stay equal.
Input schema present but exposes no named parameters.
Structured output declared, but exposes no named fields.
No examples provided.
distance_matrix Distance matrix ~396
Straight-line (great-circle) distance in meters between every origin and destination. origins and destinations are each a list of LocationRefs — a {"lat": ..., "lon": ...} dict, a GERS id, or a free-text place name, mixed freely — capped at 10 each (100 pairs max). This is a plain haversine calculation, not a routed distance or travel time, so it's cheap but it is NOT what Google/Mapbox distance-matrix APIs return: no roads, no turns, no travel time. For "how far can I get in N minutes" use isochrone() instead; for actual routed times/distances between several points use travel_time_matrix(). An id/name that failed to resolve returns an indexed error (origins[i]: ... or destinations[i]: ...) with candidates on ambiguity — checked after the 10-point cap, so an over-cap list always fails on the cap first. Any origin/destination given by id/name adds "resolved": {"origins": [{"index", "name", "id", "lat", "lon", "matched_by"}, ...], "destinations": [...]} covering just those entries; each side is present only if it had a string entry, and the whole key is absent when every point was already coordinates. Returns {"elements": [{"origin_idx": 0, "dest_idx": 0, "distance_m": 812}, ...]}, flat and origin-major (all destinations for origin 0, then origin 1, ...), budgeted like every other tool. Empty origins or destinations returns {"elements": []}. Returns a structured {"error": "bad_request", ...} instead of raising if either list exceeds 10 points or a point is missing/non-numeric lat or lon.
| Name | Type | Req | Description |
|---|---|---|---|
| destinations | array | yes | – |
| origins | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
elevation_at Elevation at a point ~244
Ground elevation in meters at a point, from Copernicus GLO-30 (~30 m resolution). Reads the Copernicus DEM directly from AWS Open Data (no API key, no third-party elevation service) — the same open-data pattern every other tool here uses, just a different bucket than Overture's. Nearest-cell sampling, not interpolated: at ~30 m ground resolution the answer is "the elevation of the DEM cell containing this point", which can be off by a few meters from the exact spot on a steep slope. Returns {"elevation_m": <float>}. No coverage at this point — open ocean, or a tile the Copernicus release excludes from public distribution — is a real, non-error answer: {"elevation_m": null, "note": "..."} explaining why. Returns a structured {"error": ...} for an out-of-range coordinate, or if the DEM tile can't be fetched (network/upstream failure). Attribution: Copernicus DEM © DLR/ESA, accessed via AWS Open Data.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
find_near Find places near a name ~272
Places of a category near a named place or city. Pass the user's place name as near. Do not call geocode(), resolve_place(), or geocode_batch() first. One hop for a category near a named landmark. Resolves near, then searches like a point find. Returns compact rows (name, category, distance, trust_note) plus the resolved near (name and coordinates). If near matches several equally-ranked places, returns {"error": "ambiguous_place", "candidates": [...]} instead of picking a city. An unresolvable name returns {"error": "not_found"}; empty category or near returns {"error": "bad_request"}. radius_m and limit follow the same clamps as a point search. A truncated answer carries "cursor" (delegated straight through from find_places); pass it back with the same category/near/radius_m/limit to continue. See find_places' docstring for the bad_cursor/release- mismatch details — they apply here unchanged.
| Name | Type | Req | Description |
|---|---|---|---|
| category | string | yes | – |
| cursor | – | – | Continuation cursor from a previous truncated answer; valid for the same query on the same data release. |
| limit | integer | – | – |
| near | string | yes | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
find_places Find places ~1,998
Find named places, either near a point or inside an area's boundary. Three mutually exclusive modes: - Point + radius: pass lat and lon (radius_m defaults to 1000m). Results are nearest-first, within a circle around (lat, lon). - Division polygon: pass division_id (a GERS division id, e.g. one from an admin-hierarchy chain) instead of lat/lon. Results are every matching place whose point falls inside that division's true boundary polygon — no radius to guess, and no circle clipping a coastline or straddling a border. Results are ordered by name (there's no reference point to rank distance from). - Area by name: pass area ("Palo Alto") to get the division-polygon mode without first resolving the id yourself. The name is resolved with the same ranking geocode/resolve_place use; the resolved division is echoed back as "area" on the response so it's clear which one was searched. A name matching several equally-ranked divisions returns {"error": "ambiguous_area", "candidates": [...]} listing their division_ids rather than silently picking one, and an unresolvable name returns {"error": "not_found"} rather than an empty result that would read as "this place has no coffee shops". category matches Overture's taxonomy (e.g. 'coffee_shop', 'restaurant', 'grocery'); name is a substring match on the place name — both compose with either mode. Results include operating_status ("in business" / "permanently closed" / null when unknown) — a business-lifecycle signal, NOT opening hours; this data has no open-now information — and a compact trust_note calibrated from confidence and that status. min_confidence (0.0-1.0) keeps only rows whose confidence score is at least that value; out-of-range values return a bad_request error. operating_status filters to a single status (see the schema's enum for accepted relabeled/raw values); "permanently closed"/"closed" also match Overture's separate "closed_permanently" raw value, since both relabel the same way.…
| Name | Type | Req | Description |
|---|---|---|---|
| area | – | – | – |
| brand | – | – | – |
| categories | – | – | – |
| category | – | – | – |
| confirm | boolean | – | – |
| cursor | – | – | Continuation cursor from a previous truncated answer; valid for the same query on the same data release. |
| detail | – | – | Row detail tier for find_places rows: 'ids' (id + distance_m only), 'compact' (id/name/category/lat/lon/distance_m/trust), or 'full' (every field, incl. trust_note prose). Default: compact. |
| division_id | – | – | – |
| group_by_category | boolean | – | – |
| has_phone | – | – | – |
| has_website | – | – | – |
| lat | – | – | – |
| limit | integer | – | – |
| lon | – | – | – |
| min_confidence | – | – | – |
| name | – | – | – |
| operating_status | – | – | Business-lifecycle status filter (relabeled or raw Overture value, case-insensitive). Default: no filter. |
| radius_m | number | – | – |
| within | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
from_to Named-place route ~493
Shortest-path walk, cycle, or drive between two places. Pass each of from/to as a free-text place name, a {"lat", "lon"} dict, or a GERS id — mixed freely. Do not call geocode(), resolve_place(), or geocode_batch() first. Plain names resolve in parallel exactly as before; coordinates pass through untouched. Builds one street graph and returns distance, duration, export maps/gpx/text, and a "from"/"to" block carrying whatever the input resolved to (name/id when it was a name or GERS id, lat/lon always). If a name matches several equally-ranked places, returns {"error": "ambiguous_place", "candidates": [...]} instead of picking a city. If the two ends resolve a city apart, returns {"error": "too_far"} with the resolved ends and the mode cap rather than extracting a continent graph. Same per-mode straight-line caps as a coordinate route (walk ~7.5 km, cycle ~23.5 km, drive ~95.5 km). An unresolvable name or GERS id returns {"error": "not_found"}; a malformed from/to (empty string, dict missing lat/lon, wrong type) returns {"error": "bad_request"} — either way the offending side is named in "field": "from" | "to". Omit mode to use the stored preferences mode, else walk. include_path, include_elevation, and prefer pass straight through to route() — see that tool's docstring for what each returns/means ("elevation" climb profile, prefer="flat" grade-avoiding preference and its honest step-free/accessibility caveats). confirm=true after the user agreed to wait for a first-time street-graph build (about 5–25 seconds). Pass it only after a needs_confirm reply and they said yes. A warm or cached graph never needs it. Omit confirm unless you just asked and they said yes.
| Name | Type | Req | Description |
|---|---|---|---|
| confirm | boolean | – | – |
| from | – | yes | – |
| include_elevation | boolean | – | – |
| include_path | boolean | – | – |
| mode | – | – | Travel mode. Default: stored preference, else walk. |
| prefer | – | – | Grade preference. Default: none (plain-distance routing). |
| to | – | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
geocode Geocode a place name ~787
Free-text place name -> ranked candidate locations, from Overture divisions and places. No Nominatim, no third-party geocoding API. Matches localities, neighborhoods, regions, and countries by name (exact > prefix > substring), falling back to named places if that doesn't fill `limit`. Returns {"results": [{name, type, lat, lon, id (GERS), admin_context, rank_score}, ...]}, budgeted like every other tool. Returns a structured {"error": ...} instead of raising if the remote scan fails. A query with no location context in it at all (a bare place name that matches no division, e.g. "Blue Bottle Roastery") can't be bounded to a region, so the places half of the search is skipped rather than scanning the global places dataset — minutes, not seconds (#105). That case comes back empty with a "note" saying so and what to do instead. A misspelled name that matches no division literally ("Berekley", or "Berekley, CA" — the region suffix is set aside first) gets one close-spelling retry over the local divisions table (#215); those results rank below any literal match, carry "matched_by": "fuzzy", and come with a "note" naming the spelling they were corrected to. A query that is entirely a postcode ("94110", "1011AB") is answered as one (#223): one result per country whose address points carry that code, with "type": "postcode", "country", "address_count" and a null "id" (a postcode is not a GERS entity). Codes are shared across countries far more often than not, so the alternates below the top row are real ambiguity. The accompanying "note" carries the granularity caveat (a Dutch code is a street block, a US ZIP a district) and the coverage limits -- including the countries the addresses theme covers but that carry no postcode values at all, which is why a valid postcode can still come back empty. Exonyms work too (#214): names are matched against Overture's ~100 localized alternates as well as its canonical one, so "Munich" answers München and "Tokyo" answers 東京都. `name…
| Name | Type | Req | Description |
|---|---|---|---|
| lang | – | – | Result-language code (2-3 lowercase letters, e.g. "de"). Overture-tagged name variants only — never transliterated or invented. Default: stored preference, else the primary name. |
| limit | integer | – | – |
| query | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
geocode_address Find a street address ~533
Street address -> coordinates: "1600 Amphitheatre Parkway, Mountain View". The forward counterpart to address_at, and finer than geocode, which answers at city/neighborhood granularity and never at a doorway. The first comma splits the street from the city; a bare integer at either end of the street part is the house number ("1600 Amphitheatre Parkway", "Hauptstraße 5"). Pass `number`/`street`/`city` instead if you already have the parts. Unit/apartment numbers are not parsed. The city is resolved first and its boundary bounds the search, so a city that resolves to no boundary — or to something far larger than a city, like a state — returns an empty list plus a note rather than a scan. If a same-named runner-up in the same country supplies the boundary instead, the note names it — the answer is never silently about a different city, and never about one in another country. Check `anchor` (name, country, admin_context) to see which one it was. Street names match in either spelling (Parkway/Pkwy, West/W, NW/Northwest). Returns {"results": [{number, street, unit, postcode, country, distance_m, lat, lon}, ...], "anchor": {name, id, country, admin_context}, "match": "exact"|"nearest_number"|"street"}, deduplicated to distinct number+street+postcode and nearest the city's own point first. More matches than `limit` adds "truncated", "distinct_in_range" and a note. `match` is absent only when no street was scanned at all (no street name, no city, or an unresolved anchor). A requested number with no address point is never interpolated: when the street has other numbered points, `results` holds the real nearest known numbers bracketing the miss instead (`match: "nearest_number"`, each row its own genuine coordinates, plus a note naming the miss and neighbors) — never a synthesized coordinate for the missing number. No usable numbers on the street falls to `match: "street"`, today's empty-plus-note. Coverage is alpha: 39 countries, no UK, Ireland, India or China. An empty…
| Name | Type | Req | Description |
|---|---|---|---|
| city | – | – | – |
| limit | integer | – | – |
| number | – | – | – |
| query | string | – | – |
| street | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
geocode_batch Geocode names in batch ~225
Geocode up to 20 free-text queries in one call, one best match each. Cuts N round-trips of geocode() into one and, more importantly, shares ONE local divisions name table across the batch (#329) so a two-name walk is not N cold S3 scans. For each query, keeps only the top candidate. Returns {"results": [{"query", "name", "type", "lat", "lon", "id" (GERS), "rank_score"}, ...]}, one row per query, in input order — a query with no match gets the standard error envelope {"query", "error": "not_found", "detail"} instead, and does not fail the rest of the batch. queries is capped at 20; a longer list returns a structured {"error": ...} rather than truncating silently. Budgeted like every other tool. Returns a structured {"error": ...} instead of raising if the remote scan itself fails.
| Name | Type | Req | Description |
|---|---|---|---|
| limit_per_query | integer | – | – |
| queries | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
geometry_op Geometry operations ~816
Geometry math and predicates — one tool, many ops, no Overture scan. `op` selects the operation; pass only the params it needs (points are `{"lat": ..., "lon": ...}`; `geometry` is a GeoJSON object): - `distance(point, point2)` -> `{"distance_m"}` (great-circle haversine distance) - `bearing(point, point2)` -> `{"bearing_deg"}` (initial compass bearing) - `destination(point, bearing_deg, distance_m)` -> `{"point"}` - `midpoint(point, point2)` -> `{"point"}` (great-circle midpoint) - `area(geometry)` -> `{"area_m2", "area_km2"}` (Polygon/MultiPolygon) - `length(geometry)` -> `{"length_m"}` (LineString/MultiLineString) - `bbox(geometry)` -> `{"bbox": [xmin, ymin, xmax, ymax]}` (any geometry) - `centroid(geometry)` -> `{"point"}` (any geometry) - `buffer(point, radius_m)` -> `{"geometry"}` (Polygon, ~32-vertex circle approximation) - `convex_hull(points)` -> `{"geometry"}` (Polygon; points capped at 100) - `point_in_polygon(points, geometry)` -> `{"results": [bool, ...]}` (Polygon/MultiPolygon, holes honored; points capped at 100) - `nearest_point(point, points)` -> `{"index", "distance_m"}` (points capped at 100) - `nearest_point_on_line(point, geometry)` -> `{"point", "distance_m", "fraction"}` (LineString) - `union(geometry, geometry2)` -> `{"geometry", "area_km2"}` (Polygon/MultiPolygon, either slot) - `intersect(geometry, geometry2)` -> `{"geometry", "area_km2"}`, or `{"empty": true, "note"}` when the two inputs don't overlap - `difference(geometry, geometry2)` -> `{"geometry", "area_km2"}` (geometry minus geometry2), or `{"empty": true, "note"}` when geometry2 fully covers geometry `buffer`, `convex_hull`, and `union`/`intersect`/`difference` are the ops that return geometry; that output is simplified to fit the same token budget `simplify_geometry`'s own default targets, so there's no need to chain a second call. `union`/`intersect`/`difference` run via the DuckDB spatial extension already loaded for other tools (see geometry_setops.py) rather than geo…
| Name | Type | Req | Description |
|---|---|---|---|
| bearing_deg | – | – | – |
| distance_m | – | – | – |
| geometry | – | – | – |
| geometry2 | – | – | – |
| op | string | yes | Geometry operation; each takes a different subset of the other arguments — see below. |
| point | – | – | – |
| point2 | – | – | – |
| points | – | – | – |
| radius_m | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
gers_lookup Look up a GERS id ~392
Any GERS id -> what it is, across themes, plus its cheap cross-theme joins. The reverse of every other tool: hand back an id one of them returned (a place, a division, or a building) and get the entity it names — {"id", "theme", "type", "name", "lat", "lon", "summary", "related"} — without needing to know which theme it came from. summary carries a few theme-specific fields (place: category, confidence, brand; division: subtype, country, region; building: class, height, floors); related carries the containing division, plus the building at the point when the id is a place. Never geometry. Also pass near_lat/near_lon — the lat/lon of the row the id came from — whenever you have them: the lookup is an id scan across up to three themes, and the hint narrows each one to a ~50km box instead of a full-theme scan. Omitting it still works, just much slower on a cold id. The hint *bounds* the search rather than merely ordering it: an id outside the box comes back not_found with a note saying so, and the exhaustive lookup is the same call without near_lat/near_lon. Pass a hint you are sure of, or none at all. Transportation segment/connector ids are not resolvable yet and come back as not_found. Returns {"error": "not_found"} if no theme claims the id, {"error": "bad_request"} for a malformed id (a GERS id is an opaque token — 32 lowercase hex characters) or an out-of-range hint, or a structured {"error": ...} if upstream is unavailable.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | – |
| near_lat | – | – | – |
| near_lon | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
ground_location Ground a location ~483
One-hop location grounding: where, surroundings, reach, notable. Answers "orient me at this point" in a single call instead of chaining a reverse lookup, an area summary, a reachable-area scan, and a nearby-places search. Give the point as lat/lon, or as `where` — a {"lat", "lon"} dict, a GERS id, or a free-text place name — but not both (and not neither); either way returns {"error": "bad_request"} naming the choice. A `where` given as an id/name adds a compact "resolved": {"name", "id", "lat", "lon", "matched_by"} to the answer (a separate key from the answer's own "where" section below); absent for lat/lon or a {lat,lon} where. Returns: - where: reverse_geocode's answer for the point (address/divisions chain, or a "divisions_only" degrade). - surroundings: total places and the top few categories within a fixed 500m radius, plus density_per_km2. - reach: reachable-area stats only for (minutes, mode) — {reachable_nodes, max_radius_m, area_km2}. Never includes the reachable-area polygon; this tool returns no geometry, ever. - notable: the nearest 2-3 named places, no category filter. Each section is independent: if its underlying call fails or comes back empty, that section is dropped and a short line explaining why is added to "notes" instead — the call only fails outright if every section failed, returning a structured {"error": "upstream_unavailable", ...}. minutes must be > 0 and <= 60; omit mode to use the stored preferences mode, else walk. Both, plus out-of-range coordinates, return {"error": "bad_request"}. No confirm gate: the reach scan runs with the requested minutes/mode as-is (it self-caps its graph extraction radius; no nearby street graph just degrades the reach section to a note).
| Name | Type | Req | Description |
|---|---|---|---|
| lat | – | – | – |
| lon | – | – | – |
| minutes | number | – | – |
| mode | – | – | Travel mode. Default: stored preference, else walk. |
| where | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
infrastructure_at Infrastructure near a point ~498
Infrastructure near a point, nearest first: bridges, towers, piers — and street furniture. From Overture's base theme (issue #179), type=infrastructure — the built things that are neither buildings nor POIs. Read the data honestly before trusting an answer: this layer is dominated by street furniture (street_lamp, bench, waste_basket, bollard, kerb, crossing), which outnumbers landmark infrastructure roughly 50:1 in a city centre. An unfiltered query in a dense area returns lamps and benches and says nothing about whether a bridge is nearby. To ask about landmarks, filter: subtype/infra_class match Overture's `subtype` and `class` columns (case-insensitive substring; infra_class is `class` under a non-reserved name) — e.g. subtype="bridge", subtype="tower", subtype="power", infra_class="pier". Returns {"center", "radius_m", "results": [{"id", "subtype", "class", "name", "distance_m"}, ...]}, plus "truncated": true, "total_in_range" and an explanatory "note" whenever more features matched than were returned. id is the GERS id, usable with other GERS-keyed tools. No raw geometry (design rule: answers, not data). Radius search, not containment: most infrastructure is linear or a bare point, so "what's within radius_m" is the answerable question. distance_m is measured to the closest point on the feature, not its centroid — a bridge you are standing on reads ~0 m, not "distance to the middle of the bridge". radius_m echoes the effective radius, which may be lower than requested (large values are clamped). An empty results list is a valid answer, not an error: base-theme coverage is OSM-derived and patchy, and "no infrastructure within 500 m" is a real finding. Returns a structured {"error": ...} if upstream is unavailable or the dataset is missing geometry/bbox, and {"error": "bad_request"} for a non-finite or out-of-range coordinate.
| Name | Type | Req | Description |
|---|---|---|---|
| infra_class | – | – | – |
| lat | number | yes | – |
| limit | integer | – | – |
| lon | number | yes | – |
| radius_m | number | – | – |
| subtype | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
isochrone Reachable area (isochrone) ~501
Isochrone: the area reachable from a point within `minutes`, by mode. Give the point as lat/lon, or as `where` — a {"lat", "lon"} dict, a GERS id, or a free-text place name — but not both (and not neither); either way returns {"error": "bad_request"} naming the choice. A `where` given as an id/name adds a compact "resolved": {"name", "id", "lat", "lon", "matched_by"} to the answer; absent for lat/lon or a {lat,lon} where. Builds a street graph from Overture's transportation theme and runs Dijkstra out to the time budget. Each mode excludes its own set of unusable road classes (e.g. drive excludes footway/path/steps; cycle and drive exclude motorway/trunk... drive itself allows motorways) and respects one-way restrictions for cycle/drive (walk ignores them). speed_m_s overrides the mode's default speed model (walk 1.4 m/s, cycle 4.2 m/s, drive per-edge from Overture's speed_limits or a class-based default table) with a single constant. Returns {"polygon": <GeoJSON Polygon>, "stats": {reachable_nodes, max_radius_m, area_km2}, ...}. The polygon traces the boundary of reached nodes' occupied grid cells (falling back to a convex hull for very small reachable sets); reachable_nodes/ max_radius_m are always exact, only the drawn polygon shape approximates, and is decimated/simplified to fit the token budget. radius_m optionally overrides the auto-derived graph extraction radius (capped per mode: 5km walk, 15km cycle, 60km drive); passing something larger than the cap returns a structured error instead of silently truncating. An unrecognized mode string returns a structured {"error": "unsupported_mode"}. minutes must be > 0 and radius_m (if given) must be >= 0, else returns {"error": "bad_request"}.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | – | – | – |
| lon | – | – | – |
| minutes | number | – | – |
| mode | – | – | Travel mode. Default: stored preference, else walk. |
| radius_m | – | – | – |
| speed_m_s | – | – | – |
| where | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
land_use_at Land use at a point ~271
What kind of land is this: land use and land cover classification at a point. From Overture's base theme (issue #167) — PlaceRoot's first tool over base, distinct from the place-search and area-summary tools (those cover discrete POIs, not the land itself). Returns {"lat", "lon", "land_use": {"subtype", "class", "name"} or null, "land_cover": {"subtype", "class"} or null}. No raw geometry (design rule: answers, not data). null for either field means no polygon of that type covers the point — coverage is OSM-derived and patchy outside well-mapped cities, so this is a common, valid answer for a rural or remote point, not an error. When multiple polygons overlap (Overture nests them, e.g. a park inside a residential parcel), the smallest/most specific one is returned and a "note" flags that the pick was made among several valid candidates. Returns a structured {"error": ...} if upstream is unavailable or a base-theme dataset is missing geometry/bbox, and {"error": "bad_request"} for a non-finite or out-of-range coordinate.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
meeting_point Meeting point ~958
Where several people should meet, fairly: candidate venues ranked by equalized travel time, not geometric distance. Fairness objective: minimize the MAXIMUM per-person travel time to the venue ("no one gets screwed"), tie-broken by the smaller spread (max - min across everyone), then by the smaller total. This is deliberately not "minimize the average" — that objective can strand one person with a long trip so two others get a short one. origins is 2-5 points, each a {"lat": ..., "lon": ..., "mode": ...} dict, a GERS id, or a free-text place name, mixed freely — mode is "walk", "cycle", or "drive", defaulting to "walk" when omitted (a string origin always gets the default mode; give a dict with "mode" to pick otherwise), and can differ per person (e.g. one driving, one walking). An id/name that failed to resolve returns an indexed error (origins[i]: ...) with candidates on ambiguity. Any origin given by id/name adds "resolved": [{"index", "name", "id", "lat", "lon", "matched_by"}, ...] for just those origins; absent when every origin was already coordinates. category optionally filters candidate venues to an Overture taxonomy slug (e.g. 'coffee_shop'); a wrong or unrecognized slug is a silent zero-match, not an error. Method: a seed center is computed from each origin's implied straight-line travel time (not raw distance, so a walking participant pulls the center toward them more than a driving one at the same distance), venues are searched for near that seed, and each candidate's real per-person times come from routing.route() — the exact routed number, not the seed's approximation. The total routed (candidate, origin) fan-out is capped at 16 pairs, regardless of `limit` — 8 candidates at 2 origins, down to 3 candidates at 5. Returns {"center": {"lat", "lon"}, "candidates": [{"id", "name", "category", "lat", "lon", "per_person": [{"origin_idx", "mode", "travel_time_min", "distance_m"}, ...], "max_travel_time_min", "spread_min"}, ...]}, ranked fairest-first, ca…
| Name | Type | Req | Description |
|---|---|---|---|
| category | – | – | – |
| confirm | boolean | – | – |
| limit | integer | – | – |
| origins | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
neighborhood_verdict Neighborhood verdict ~225
Life-decision neighborhood verdict, not a data dump. Accepts a point plus free-form life context (household, mobility, priorities) and returns a ranked verdict: strengths, weak points, and the one thing to verify in person. Empty context still answers a generic walk-first daily-needs check and says what was assumed. Optional radius_m / minutes / mode override what the context implies (no car / walk-first -> walk, bike -> cycle, car -> drive; default walk, 15 minutes). Does not call out to extra remote APIs. Returns a structured {"error": ...} for bad coordinates, an unknown mode, a radius past the mode cap, upstream failure, or a degraded schema. Missing street graph degrades to straight-line times with a note rather than failing the verdict.
| Name | Type | Req | Description |
|---|---|---|---|
| context | string | – | – |
| lat | number | yes | – |
| lon | number | yes | – |
| minutes | – | – | – |
| mode | – | – | Travel mode override. Default: inferred from context, else walk. |
| radius_m | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
optimize_route Best visiting order for stops ~792
Best order to visit several stops: multi-stop route ordering (a small TSP). Answers "I have these five errands, what order costs least" — stops is a list of 2-10 points, each a {"lat": ..., "lon": ..., "name": ... (optional)} dict, a GERS id, or a free-text name, mixed freely. The answer is the cheapest visiting order over the real street graph, not a straight-line guess. Solved exactly (Held-Karp over the routed cost matrix), so it is the optimum, not a nearest-neighbour approximation. Any stop given by id/name that failed to resolve returns an indexed error (stops[i]: ...) with candidates on ambiguity — the whole call fails, not silently drops that stop. Returns {"order": [stop indices, in visiting order], "legs": [{"from_idx", "to_idx", "distance_m", "duration_s"}, ...], "total_distance_m", "total_duration_s", "mode", "roundtrip", "export"} — indices refer to the input `stops` list, and there is no polyline/geometry — for a single pair's numbers on their own, call `route`. export is the pocket handoff: a multi-stop Google/Apple Maps directions URL (coordinates only — no Maps API), a GPX 1.1 document with every stop as a waypoint, and a printable list that keeps any names the caller passed. If a stop already carries confidence or operating_status (from a prior place lookup), the response adds verify_before_going naming the 1–2 weakest. Any stop given as an id or name adds "resolved": [{"stop": i, "name", "id", "lat", "lon", "matched_by"}, ...] for just those stops — plain {lat,lon} stops need no echo and the key is absent when every stop was already coordinates. start_index (default 0) is fixed as the first stop. roundtrip=true (the default) returns to it; the closing leg is in "legs" but the start is not repeated in "order". roundtrip=false is an open path that ends wherever is cheapest. Omit mode to use the stored preferences mode, else drive. Same cost model every routing tool uses; one-ways make the drive/cycle cost matrix asymmetric and that is solved for…
| Name | Type | Req | Description |
|---|---|---|---|
| mode | – | – | Travel mode. Default: stored preference, else drive. |
| roundtrip | boolean | – | – |
| start_index | integer | – | – |
| stops | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
place_details Place details ~400
One place, in full: addresses, websites, phones, socials, brand, source attribution, GERS id, confidence, operating status, and a compact trust_note. Resolve either by GERS id (the `id` field find_places and other tools return) or by name + lat/lon (nearest name match within radius_m of that point). Pass id, or pass name together with lat and lon — not both. Long array fields (addresses, websites, phones, socials, sources) are capped and never silently dropped: a truncated field carries a matching "<field>_omitted_count". Returns {"error": "not_found", ...} if nothing matches, or a structured {"error": ...} if the upstream dataset is unavailable or missing columns this tool depends on. When looking up by id, also pass near_lat/near_lon — the lat/lon from the find_places (or other tool) row the id came from — so the lookup can be narrowed to a ~50km box instead of scanning the whole dataset. Ignored when resolving by name. Omitting it still works, just slower on a cold, uncached id. lang (#410) requests Overture's language-tagged name variant for this place, when the data has one: `name` becomes the variant and `name_primary` is added only when it differs. Default: the stored `preferences()` lang, else the primary name unchanged. Never invented or transliterated.
| Name | Type | Req | Description |
|---|---|---|---|
| id | – | – | – |
| lang | – | – | Result-language code (2-3 lowercase letters, e.g. "de"). Overture-tagged name variants only — never transliterated or invented. Default: stored preference, else the primary name. |
| lat | – | – | – |
| lon | – | – | – |
| name | – | – | – |
| near_lat | – | – | – |
| near_lon | – | – | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
places_along_route Places along a route ~543
Places on the way from A to B: corridor search along the route. Answers "find a coffee shop on my drive to the airport" — the route tool plus find_places in one call. Builds the same street-graph shortest path `route` returns, then finds places whose nearest point on that path is within max_detour_m (default 1000m, capped at 5000m; larger values return a bad_request error rather than being silently clamped). Each result row is a find_places row plus two numbers: detour_m, the straight-line distance to the route doubled — an approximation of the round trip off and back on, not a re-routed detour — and along_m, how far along the route from the origin that place sits, so "roughly halfway" is answerable. Results are ordered by along_m (route order, reading as an itinerary) rather than by detour cost. When more than limit places are on the way, the response is an even sample spanning the whole route — never just the first limit, which would drop the far end of the journey — and carries "truncated": true saying so. It also carries {"route": {"distance_m", "duration_s", "mode"}} for the underlying route. A composed itinerary also carries verify_before_going when any stop is low-confidence or listed closed, naming the 1–2 places most worth checking. category and name narrow the search exactly as they do in find_places (category matches Overture's taxonomy, e.g. 'coffee_shop'; name is a substring match) — worth passing on a long route, since an unfiltered corridor through a dense area can hold more places than the search considers, in which case the response carries "truncated": true and a note saying so. Omit mode to use the stored preferences mode, else drive. Same cost model and the same straight-line-distance caps as `route`, and the same structured errors: route_too_long, no_graph_nearby, no_route, unsupported_mode, and bad_request for non-finite/out-of-range coordinates or an invalid max_detour_m.
| Name | Type | Req | Description |
|---|---|---|---|
| category | – | – | – |
| from_lat | number | yes | – |
| from_lon | number | yes | – |
| limit | integer | – | – |
| max_detour_m | number | – | – |
| mode | – | – | Travel mode. Default: stored preference, else drive. |
| name | – | – | – |
| to_lat | number | yes | – |
| to_lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
preferences Persistent preferences ~271
Travel defaults. State "I bike everywhere, I have a dog" once. Routing tools use the stored mode when you omit theirs; an explicit argument always wins. pace and household are stored for later features and do not change answers yet. lang (#410) is the stored result-language preference: the name-lookup tools that accept their own `lang` use this one when theirs is omitted, returning an Overture-tagged name variant (e.g. "Munich" for "München" with lang="en") — a per-call `lang` always wins. The same document is the placeroot://preferences resource. Call with no arguments to read. Pass mode, pace, household tags, a free-text note, or lang to merge those fields. clear=true deletes the file and cannot be combined with other fields. Nothing is sent off this machine.
| Name | Type | Req | Description |
|---|---|---|---|
| clear | boolean | – | – |
| household | – | – | – |
| lang | – | – | Result-language code (2-3 lowercase letters, e.g. "de"). Overture-tagged name variants only — never transliterated or invented. Default: stored preference, else the primary name. |
| mode | – | – | Travel mode to store. Omit to leave it unchanged. |
| note | – | – | – |
| pace | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
render_map Render map ~586
Render any result as a shareable one-pager: map, verdict, and stop list. Writes ONE self-contained HTML file — interactive SVG map (inline CSS/JS, vector markers with labels and click popups, polygon/line shapes including reachability output shaped {"polygon": ..., "stats": {...}}), a composed verdict, per-stop details, a scale bar, and required attribution. A shape feature's properties may carry "role": "shed" (soft translucent fill, dashed edge — for travel-time sheds) or "role": "outline" (no fill, strong edge — for a compared-area boundary); any other/absent role keeps the default style. Properties may also carry a short "label" and one-line "callout", rendered as a text chip over the shape (capped ~40/~80 chars); for the reachability payload, set role/label/callout at the payload's top level. No CDN, no tile server, no API key, zero network requests when opened — a local file the user can send as-is. Pass `summary` for the verdict you want on the page (the sentence you'd tell a spouse, co-founder, or landlord); when omitted a short fallback is composed from the payload. Written to PLACEROOT_ARTIFACT_DIR (default: alongside the tile cache directory). The file itself is the artifact; this tool's response stays small on purpose. Returns {"path", "bytes", "features_rendered", "skipped_features"} (plus "truncated": True when applicable) — skipped_features counts rows/features that couldn't be rendered (missing coordinates, malformed geometry, or dropped past mapview.MAX_RENDER_VERTICES) rather than failing the call outright. Pass inline=true to also get the HTML back in the response when it's small enough to be worth it. A point in `result` carrying a "class" property gets a contrasting marker dot when `legend` maps that class to {"label": str, "color": str?} — pass e.g. {"open": {"label": "Open now"}, "closed": {"label": "Closed", "color": "#d55e00"}}. A missing color is assigned from a fixed color-blind-safe palette; an invalid one (not #rgb/#rrggbb hex) is dro…
| Name | Type | Req | Description |
|---|---|---|---|
| inline | boolean | – | – |
| legend | – | – | – |
| result | – | yes | – |
| summary | – | – | – |
| title | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
resolve_place Resolve place to GERS id ~803
Free-text place reference -> ranked, typed GERS ids to hold onto. Turns something like "the Whole Foods on Lamar" or "Travis County" into stable Overture ids: merges geocode()'s division matches (locality, region, county, country, ...) with a name-filtered find_places search (a business or POI), bbox-limited to near_lat/near_lon if given, else to the ~20km vicinity of the top division match. **Split the location out of the query, and pass `city`.** You know things this server does not: that "san jose airport" means San Jose, California, that the Eiffel Tower is in Paris, that a user asking about "BASIS Silicon Valley" means Sunnyvale. This server knows only what exists at which coordinates in the current Overture release. When the location arrives inside one string, it has to guess which words are the place — and it guesses from map data alone, where "san" names a division in Henan and "palo" names one in Leyte. Given `city="San Jose, CA"` and `query="airport"` there is nothing to guess. A wrong hint costs a miss and a retry, never a wrong answer: `city` only bounds where the search looks, and the returned rows still come from the data. Pass `near_lat`/`near_lon` instead when you have real coordinates — they are the strongest hint of all. When nothing resolves for want of a location, the reply carries `need: "location"` and a `retry_with` sketch rather than only prose, so the second call can be made without parsing English. Returns {"results": [{"id" (GERS), "kind": "division" | "place", "name", "lat", "lon", "match": "exact" | "prefix" | "substring" | "fuzzy", plus "admin_context" for a division or "category" for a place}, ...]}, ranked by match tier then prominence ("fuzzy" — reached by close spelling rather than by containing the query at all, #215 for divisions and #373 for places — ranking below every literal match). A place found through #373's alt-spelling/typo fallback additionally carries "matched_by": "alt_name" | "fuzzy", and a top-level "note" name…
| Name | Type | Req | Description |
|---|---|---|---|
| city | – | – | – |
| lang | – | – | Result-language code (2-3 lowercase letters, e.g. "de"). Overture-tagged name variants only — never transliterated or invented. Default: stored preference, else the primary name. |
| limit | integer | – | – |
| near_lat | – | – | – |
| near_lon | – | – | – |
| query | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
resolve_place_batch Resolve GERS ids in batch ~240
Resolve up to 25 GERS ids to compact place rows in one call. Collapses N place_details(id=...) round-trips into one: for each id, resolves it via the same lookup place_details uses and keeps only a compact row — {"gers_id", "name", "category", "lat", "lon"} — not the full place_details payload (addresses, websites, phones, socials, sources, brand, confidence, ...). Use place_details for full detail on a single id. Results are returned in input order; an id that doesn't resolve gets the standard error envelope {"gers_id", "error": "not_found", "detail"} instead and does not fail the rest of the batch. gers_ids is capped at 25; a longer list returns a structured {"error": ...} rather than truncating silently. An empty list returns {"results": []}. Budgeted like every other tool. Returns a structured {"error": ...} instead of raising if the remote scan fails or the places dataset is missing columns this tool depends on.
| Name | Type | Req | Description |
|---|---|---|---|
| gers_ids | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
reverse_geocode Reverse geocode a point ~114
Point -> nearest address (street/number/postcode) and its containing division chain. Degrades to a divisions-only result (source: "divisions_only", plus a note) if the addresses theme is unreachable, missing, or has no nearby coverage — addresses is Overture's newest, least complete theme, so this is the expected degraded path. Returns a structured {"error": ...} instead of raising if the remote scan fails outright.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
reverse_geocode_batch Reverse geocode points in batch ~175
Reverse-geocode many points in one call, to cut N round-trips down to one. Accepts at most 20 points; a longer list returns a structured {"error": "bad_request"} instead of processing anything. Returns one row per point in `points`, in the same order — each row is whatever reverse_geocode(lat, lon) returns (address/divisions chain, or a "divisions_only" degrade — see reverse_geocode's docstring). A malformed point (missing/non-numeric lat/lon, or a lat/lon out of range) doesn't fail the whole batch — it yields the standard error envelope {"lat", "lon", "error": "bad_request", "detail"} in its slot instead.
| Name | Type | Req | Description |
|---|---|---|---|
| points | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
route Route between two points ~1,290
Route: shortest-path distance and duration between two points, by mode. Compact directions, not turn-by-turn: builds a street graph from Overture's transportation theme around the two points and returns {"distance_m", "duration_s", "mode", "from", "to", "export"} for the fastest path — no polyline unless you ask for one. export is the pocket handoff: Google/Apple Maps directions URLs built from the same two coordinates (URL schemes only — no Maps API, no extra network), a GPX 1.1 document, and a printable stop list. Same cost model every routing tool uses (walk 1.4 m/s, cycle 4.2 m/s, drive per-edge from Overture's speed_limits or a class-based default table). drive's duration is a posted-speed model with no live traffic; all modes snap each endpoint to the nearest usable street-graph node (real routes rarely start/end exactly on a segment). Each mode has a straight-line-distance cap on the two points, rejected before any graph is built (see routing.ROUTE_MAX_STRAIGHT_LINE_M, derived per-mode from the shared graph-extraction radius cap — roughly walk 7.5km, cycle 23.5km, drive 95.5km) — real road distance only ever exceeds straight-line, so anything past the cap can't produce a route worth extracting for anyway; returns {"error": "route_too_long"} with the exact cap in "max_distance_m". An unrecognized mode string returns {"error": "unsupported_mode"}; non-finite or out-of-range coordinates (lat outside [-90, 90], lon outside [-180, 180]) return {"error": "bad_request"}. If no usable graph or street node is found near either point, returns {"error": "no_graph_nearby"}. If both points snap into the graph but no path connects them (e.g. disconnected islands of road data), returns {"error": "no_route", "try": ...} rather than raising — "try" is a mode-tuned next move (roadmap §4). If the extraction graph hit its internal size cap, the result carries "truncated…
| Name | Type | Req | Description |
|---|---|---|---|
| confirm | boolean | – | – |
| from_lat | number | yes | – |
| from_lon | number | yes | – |
| include_elevation | boolean | – | – |
| include_path | boolean | – | – |
| mode | – | – | Travel mode. Default: stored preference, else drive. |
| prefer | – | – | Grade preference. Default: none (plain-distance routing). |
| to_lat | number | yes | – |
| to_lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
search_categories Search categories ~269
Free text -> valid Overture category slugs, for the `category` param the place-search and area-summary tools take. Lookup only — no geo filtering, no upstream dataset dependency; matches against a bundled snapshot of Overture's places taxonomy (pinned to schema v1.9.0). Ranks exact slug match > slug prefix > slug substring > a match on any taxonomy path segment, so close siblings like "cafe" vs "coffee_shop" both surface rather than one silently winning. If the whole query matches nothing, falls back to a lexical phrase-intent match against a curated synonym lexicon (e.g. "fix my cracked phone screen" -> mobile_phone_repair). Returns {"results": [{"slug", "path", "confidence"}, ...]} — path is the root-to-leaf taxonomy (e.g. ["eat_and_drink", "cafe", "coffee_shop"]), confidence is 0-1 and descending, budgeted like every other tool. An empty/whitespace query returns {"results": []}. limit is clamped to 0-50, matching every other tool's limit handling (out-of-range values are not an error).
| Name | Type | Req | Description |
|---|---|---|---|
| limit | integer | – | – |
| query | string | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
simplify_geometry Simplify geometry ~137
Simplify a GeoJSON geometry to fit a token budget, reporting what was lost. Works on caller-supplied GeoJSON (Polygon, MultiPolygon, LineString, MultiLineString; Points/MultiPoints pass through unchanged). Binary searches the simplification tolerance until the result fits max_tokens instead of asking the caller to guess one. Returns {"geometry": ..., "max_deviation_m": ..., "original_points": N, "kept_points": M}, or a structured {"error": "invalid_geometry", ...} for malformed input.
| Name | Type | Req | Description |
|---|---|---|---|
| geojson | object | yes | – |
| max_tokens | integer | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
suggest_areas Suggest areas ~770
Where within reach: neighborhoods ranked by travel budget + amenities. The inverse of every other area tool — instead of "describe this place", "find me a place". anchors is 1-3 {"lat", "lon", "mode"?, "minutes"?} points (mode: walk/cycle/drive, default from stored preferences; minutes: default 15). requirements is 1-8 free-text amenity/character strings, scored the same way as area_score.score_locality — "parks", "groceries", "coffee shop" resolve against the Overture taxonomy; a subjective phrase ("quiet streets", "safe neighborhood", "good schools") comes back {"measurable": false} rather than a guessed score (see "honesty" in the response). Method: the same street-graph reach analysis behind PlaceRoot's other travel-time tools computes each anchor's reachable shed; with more than one anchor, the sheds are intersected (a candidate must be reachable within EVERY anchor's own time budget, not just one — "office" and "gym" both mean both). divisions.divisions_in_polygon (#348) partitions the (intersected) shed into candidate neighborhoods/localities; each candidate is scored against requirements the same way area_score.score_locality (#349) does. Returns {"anchors": [...], "results": [{"division_id", "name", "subtype", "overlap_fraction", "lat", "lon", "travel": [{"anchor_idx", "mode", "minutes_budget", "travel_time_min", "distance_m"} or {..., "note": "unroutable"/ "no_graph_nearby"/...}, ...], "requirements": [...], "overall_score", "reason"}, ...], "honesty"}, ranked by overall_score (unmeasurable-only candidates sort last, never dropped) then overlap_fraction, capped at `limit` (1-10, default 5). division_id is a stable GERS id — chain a result into admin_lookup or summarize_area for more detail without re-running the search. No polygons in the response by default. An empty "results" list is a valid answer (e.g. two anchors' sheds don't overlap at all, or nothing in the reachable area is a neighborhood/ locality) with a "note" saying which. A per-anchor trav…
| Name | Type | Req | Description |
|---|---|---|---|
| anchors | array | yes | – |
| confirm | boolean | – | – |
| limit | integer | – | – |
| requirements | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
summarize_area Summarize area ~182
Summarize what's in an area: total places and top categories. Give the center as lat/lon, or as `where` — a {"lat", "lon"} dict, a GERS id, or a free-text place name — but not both (and not neither); either way returns {"error": "bad_request"} naming the choice. A `where` given as an id/name adds a compact "resolved": {"name", "id", "lat", "lon", "matched_by"} to the answer; absent for lat/lon or a {lat,lon} where. Returns a structured {"error": ...} instead of raising if upstream is unavailable or the dataset is missing columns this tool depends on.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | – | – | – |
| lon | – | – | – |
| radius_m | number | – | – |
| where | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
summarize_buildings Summarize buildings ~167
Summarize building footprints in an area: count, footprint area, height/floor coverage, mix. From Overture's buildings theme (issue #23). Returns count, total/mean footprint area in m^2, height_known_pct/num_floors_known_pct (height and floor count are sparse in real Overture data — this reports coverage rather than pretending every building has a value, with mean_height_m/mean_num_floors alongside when any are known), and top_subtypes/top_classes (top 10 each by count). Returns a structured {"error": ...} if upstream is unavailable or the dataset is missing geometry/bbox.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| lon | number | yes | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
travel_time_matrix Travel time matrix ~672
Routed travel time + distance between every origin and destination, by mode. origins and destinations are each a list of LocationRefs — a {"lat": ..., "lon": ...} dict, a GERS id, or a free-text place name, mixed freely — capped at 5 each (25 pairs max). Unlike distance_matrix's plain haversine, this is a real shortest-path search over Overture's open street graph — roads, one-ways, and each mode's own speed model, the same cost model route() uses for a single pair, one mode per call; omit mode to use the stored preferences mode, else walk. An id/name that failed to resolve returns an indexed error (origins[i]: ... or destinations[i]: ...) with candidates on ambiguity — checked after the 5-point cap. Any origin/destination given by id/name adds "resolved": {"origins": [{"index", "name", "id", "lat", "lon", "matched_by"}, ...], "destinations": [...]} covering just those entries; each side present only if it had a string entry, absent when every point was already coordinates. Reuses a single cached street graph across every origin and destination when every origin-destination pair fits the mode's straight-line cap and the whole point set fits one extraction circle, running one Dijkstra per origin against every destination at once rather than a search per pair — for a same-city matrix this costs about what a single isochrone does, not one route() call per pair. When the points are too spread out for one shared graph, falls back to a route() call per pair (up to 25). Returns {"mode", "elements": [{"origin_idx", "dest_idx", "duration_min", "distance_m"}, ...], "durations_note"}, flat and origin-major like distance_matrix. durations_note says these are speed-model estimates over the open street graph, not live traffic. An unroutable pair (off the street network, or on a disconnected fragment of it) gets {"duration_min": null, "distance_m": null, "note": "unroutable"} instead of failing the whole call; if every pair in the matrix is unroutable the response also carrie…
| Name | Type | Req | Description |
|---|---|---|---|
| destinations | array | yes | – |
| mode | – | – | Travel mode. Default: stored preference, else walk. |
| origins | array | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
verify_claims Verify listing claims ~761
Grade spatial listing claims ("8 min to the metro", "shops on the doorstep", "green space nearby") against real routing and places data. Free-text claim parsing needs an LLM — this tool takes already-decomposed structured checks; the verify_listing_claims prompt teaches an agent how to turn listing text into them. Each of claims (max 8; max 5 of kind travel_time, since each costs a routed call) is one of: - {"kind": "travel_time", "to_category": str|None, "to_name": str|None, "mode": "walk"|"cycle"|"drive" (default walk), "claimed_minutes": number} Finds the nearest place matching to_category (an Overture taxonomy slug) and/or to_name (a substring match), then routes to it and compares the routed minutes against claimed_minutes. - {"kind": "count_nearby", "category": str|None, "name": str|None, "radius_m": number (default 500, capped at 2000), "claimed_at_least": int} Counts matching places within radius_m and compares against claimed_at_least. - {"kind": "distance", "to_category": str|None, "to_name": str|None, "claimed_max_m": number} Straight-line distance (haversine, not routed) to the nearest match, compared against claimed_max_m. Every kind needs at least one of its category/name fields; giving neither is a bad_request. A category is an Overture taxonomy slug, matched exactly (including its taxonomy descendants), never as a substring — "park" does not match a parking garage; a name is a substring match. Verdict per claim: "confirmed" when the measured number is within the claimed number x1.15 (count_nearby: measured count >= claimed), "stretched" within x1.5 (count_nearby: count >= half the claim, floor 1), otherwise "false". A claim asserting a place exists at all, when none is found within the search bound, is "false" with a note — absence is a verdict, not an error. A claim the measurement cannot decide is "unverifiable" instead of "false": a travel_time claim whose place is found but cannot be routed to (no street graph nearby, or…
| Name | Type | Req | Description |
|---|---|---|---|
| claims | array | yes | – |
| lat | number | yes | – |
| lon | number | yes | – |
Structured output declared, but exposes no named fields.
No examples provided.
warmup_city Get to know my city ~194
Pre-cache a city. Copies places and transportation tiles into the same local cache later queries read. Does not build the routing graph (the first route still pays that cost) and does not pre-cache buildings. The warmup call is the slow one; later place searches over the area read locally. radius_m defaults to 8000 (a city core) and is capped at 25 km so a warmup cannot fan into a planet-sized tile fetch. confirm=true after the user agreed to wait for a first-time tile warmup (about 5–25 seconds). Pass it only after a needs_confirm reply and they said yes. An already-cached city never needs it. Omit confirm unless you just asked and they said yes.
| Name | Type | Req | Description |
|---|---|---|---|
| city | – | – | – |
| confirm | boolean | – | – |
| lat | – | – | – |
| lon | – | – | – |
| radius_m | number | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
water_near Water near a point ~546
Water near a point, nearest first: waterfront check, distance to river/canal/lake. From Overture's base theme (issue #200), type=water — oceans, bays, lakes, ponds, reservoirs, rivers, streams, canals, springs, pools. Returns {"center", "radius_m", "in_range_count", "results": [{"name" (when named), "subtype", "class", "distance_m", "is_salt"/ "is_intermittent" (only when true)}, ...]}, plus "truncated": true and a "note" when more matched than were returned. No raw geometry. distance_m is to the closest point on the feature, not its centroid — a canal bank you are standing on reads ~0 m. Water gets dense (an Amsterdam canal district puts hundreds of rows in a 500 m circle), which is what in_range_count and the filters are for: subtype/water_class match Overture's `subtype`/`class` columns (case-insensitive substring; water_class is `class` under a non-reserved name) — e.g. subtype="canal", subtype="river", water_class="lake". "on_water": true plus "water_body" means the point is *inside* a water polygon — a lake, a reservoir, a river. For oceans and seas that is a coarse signal: Overture cuts them into 1-degree tiles whose landward edge covers dry coastal land, so a waterfront building reads as inside the ocean. Those bodies are reported this way rather than as a bogus 0 m "nearest water" row, and no distance-to-coast is derived from them (their tile boundaries include phantom cuts through open water), which also means subtype="ocean" cannot return distance rows. Lakes and rivers carry none of that: however large, they appear in results with a real edge distance. The "note" says which case applies. An empty results list is a valid answer: coverage is OSM-derived, and "no water within 500 m" is a real finding about an arid or unmapped place. radius_m echoes the effective radius (large values are clamped). Returns a structured {"error": ...} if upstream is unavailable or the dataset is missing geometry/bbox, and {"error": "bad_request"} for a bad coordinate.
| Name | Type | Req | Description |
|---|---|---|---|
| lat | number | yes | – |
| limit | integer | – | – |
| lon | number | yes | – |
| radius_m | number | – | – |
| subtype | – | – | – |
| water_class | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
within_distance Check within distance ~379
Is the nearest place matching category/name within max_distance_m of (lat, lon)? Give the center as lat/lon, or as `where` — a {"lat", "lon"} dict, a GERS id, or a free-text place name — but not both (and not neither); either way returns {"error": "bad_request"} naming the choice. A `where` given as an id/name adds a compact "resolved": {"name", "id", "lat", "lon", "matched_by"} to the answer; absent for lat/lon or a {lat,lon} where. max_distance_m is required and must be a positive number of meters — a zero, negative, non-finite, or missing value returns {"error": "bad_request"} rather than silently searching a 0m (or omitted from a call entirely, in which case the schema itself rejects it before this tool ever runs) window and answering a confident-looking "false". Returns {"within": bool, "nearest": {...place row with id...} | None, "distance_m": float | None}. nearest is None if nothing matches within a search window capped at max_distance_m * 2 — a real match further out than that isn't found (documented, not a bug: keeps the search bounded). name is a literal substring match only — no alt-spelling or typo fallback applies here, so a misspelled name is an honest "no match", never a silent yes about a different name. Returns a structured {"error": ...} if upstream is unavailable or the dataset is missing columns this tool depends on.
| Name | Type | Req | Description |
|---|---|---|---|
| category | – | – | – |
| lat | – | – | – |
| lon | – | – | – |
| max_distance_m | number | yes | – |
| name | – | – | – |
| where | – | – | – |
Structured output declared, but exposes no named fields.
No examples provided.
What is the PlaceRoot MCP server?
PlaceRoot is an MCP server listed in the public MCP registry as io.github.chuofringer/placeroot. Grounds AI agents in Overture Maps open data, compact answers, no API key. This page covers its PyPI package (placeroot).
Is the PlaceRoot MCP server safe to use?
PlaceRoot scores 64 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 20 September 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the PlaceRoot MCP server expose?
PlaceRoot exposes 42 tools: find_places, summarize_area, place_details, within_distance, distance_matrix, and 37 more. Their descriptions and schemas cost roughly 21,138 tokens of context every time the server is loaded.
Is the PlaceRoot MCP server still maintained?
PlaceRoot is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.