Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

PlaceRoot

PYPI · PLACEROOT · 2 COMPONENTS · SCANNED SEP 20

Grounds AI agents in Overture Maps open data — compact answers, no API key.

0 this week 64 Trust /100
Trust breakdown (7 categories)

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
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
Install

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

# add to Claude Code
claude mcp add chuofringer-placeroot -- uvx placeroot
// .cursor/mcp.json
{
  "mcpServers": {
    "chuofringer-placeroot": {
      "command": "uvx",
      "args": [
        "placeroot"
      ]
    }
  }
}
// .vscode/mcp.json
{
  "servers": {
    "chuofringer-placeroot": {
      "command": "uvx",
      "args": [
        "placeroot"
      ]
    }
  }
}
# add to Codex CLI
codex mcp add chuofringer-placeroot -- uvx placeroot
// opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "chuofringer-placeroot": {
      "type": "local",
      "command": [
        "uvx",
        "placeroot"
      ],
      "enabled": true
    }
  }
}
# add to OpenClaw
openclaw mcp add chuofringer-placeroot --command uvx --arg placeroot
# ~/.hermes/config.yaml
mcp_servers:
  chuofringer-placeroot:
    command: "uvx"
    args: ["placeroot"]
// ~/.netclaw/config/netclaw.json
{
  "McpServers": {
    "chuofringer-placeroot": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "placeroot"
      ]
    }
  }
}
# add to Vellum
assistant mcp add chuofringer-placeroot -t stdio -c uvx -a placeroot
// mcp.json
{
  "mcpServers": {
    "chuofringer-placeroot": {
      "command": "uvx",
      "args": [
        "placeroot"
      ]
    }
  }
}
Changelog

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
Diagnostics

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 →

MCP tools · 42 exposed · ~21,138 tokens

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 →

Tool Tokens
address_at ~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.

NameTypeReqDescription
latnumberyes
limitinteger
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

admin_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.

NameTypeReqDescription
latnumberyes
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

buildings_at ~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.

NameTypeReqDescription
include_geometryboolean
latnumberyes
limitinteger
lonnumberyes
radius_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

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…

NameTypeReqDescription
category
from_release
limitinteger
max_lat
max_lon
min_lat
min_lon
place
to_release

Structured output declared, but exposes no named fields.

No examples provided.

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",…

NameTypeReqDescription
areasarrayyes
priorities
radius_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

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 ~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.

NameTypeReqDescription
destinationsarrayyes
originsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

elevation_at ~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.

NameTypeReqDescription
latnumberyes
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

find_near ~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.

NameTypeReqDescription
categorystringyes
cursorContinuation cursor from a previous truncated answer; valid for the same query on the same data release.
limitinteger
nearstringyes
radius_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

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.…

NameTypeReqDescription
area
brand
categories
category
confirmboolean
cursorContinuation cursor from a previous truncated answer; valid for the same query on the same data release.
detailRow 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_categoryboolean
has_phone
has_website
lat
limitinteger
lon
min_confidence
name
operating_statusBusiness-lifecycle status filter (relabeled or raw Overture value, case-insensitive). Default: no filter.
radius_mnumber
within

Structured output declared, but exposes no named fields.

No examples provided.

from_to ~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.

NameTypeReqDescription
confirmboolean
fromyes
include_elevationboolean
include_pathboolean
modeTravel mode. Default: stored preference, else walk.
preferGrade preference. Default: none (plain-distance routing).
toyes

Structured output declared, but exposes no named fields.

No examples provided.

geocode ~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…

NameTypeReqDescription
langResult-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.
limitinteger
querystringyes

Structured output declared, but exposes no named fields.

No examples provided.

geocode_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…

NameTypeReqDescription
city
limitinteger
number
querystring
street

Structured output declared, but exposes no named fields.

No examples provided.

geocode_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.

NameTypeReqDescription
limit_per_queryinteger
queriesarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

geometry_op ~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…

NameTypeReqDescription
bearing_deg
distance_m
geometry
geometry2
opstringyesGeometry 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 ~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.

NameTypeReqDescription
idstringyes
near_lat
near_lon

Structured output declared, but exposes no named fields.

No examples provided.

ground_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).

NameTypeReqDescription
lat
lon
minutesnumber
modeTravel mode. Default: stored preference, else walk.
where

Structured output declared, but exposes no named fields.

No examples provided.

infrastructure_at ~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.

NameTypeReqDescription
infra_class
latnumberyes
limitinteger
lonnumberyes
radius_mnumber
subtype

Structured output declared, but exposes no named fields.

No examples provided.

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"}.

NameTypeReqDescription
lat
lon
minutesnumber
modeTravel 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 ~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.

NameTypeReqDescription
latnumberyes
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

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…

NameTypeReqDescription
category
confirmboolean
limitinteger
originsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
contextstring
latnumberyes
lonnumberyes
minutes
modeTravel mode override. Default: inferred from context, else walk.
radius_m

Structured output declared, but exposes no named fields.

No examples provided.

optimize_route ~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…

NameTypeReqDescription
modeTravel mode. Default: stored preference, else drive.
roundtripboolean
start_indexinteger
stopsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
id
langResult-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_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

places_along_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.

NameTypeReqDescription
category
from_latnumberyes
from_lonnumberyes
limitinteger
max_detour_mnumber
modeTravel mode. Default: stored preference, else drive.
name
to_latnumberyes
to_lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
clearboolean
household
langResult-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.
modeTravel mode to store. Omit to leave it unchanged.
note
pace

Structured output declared, but exposes no named fields.

No examples provided.

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…

NameTypeReqDescription
inlineboolean
legend
resultyes
summary
title

Structured output declared, but exposes no named fields.

No examples provided.

resolve_place ~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…

NameTypeReqDescription
city
langResult-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.
limitinteger
near_lat
near_lon
querystringyes

Structured output declared, but exposes no named fields.

No examples provided.

resolve_place_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.

NameTypeReqDescription
gers_idsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

reverse_geocode ~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.

NameTypeReqDescription
latnumberyes
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

reverse_geocode_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.

NameTypeReqDescription
pointsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

route ~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…

NameTypeReqDescription
confirmboolean
from_latnumberyes
from_lonnumberyes
include_elevationboolean
include_pathboolean
modeTravel mode. Default: stored preference, else drive.
preferGrade preference. Default: none (plain-distance routing).
to_latnumberyes
to_lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

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).

NameTypeReqDescription
limitinteger
querystringyes

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
geojsonobjectyes
max_tokensinteger

Structured output declared, but exposes no named fields.

No examples provided.

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…

NameTypeReqDescription
anchorsarrayyes
confirmboolean
limitinteger
requirementsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
lat
lon
radius_mnumber
where

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
latnumberyes
lonnumberyes
radius_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

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…

NameTypeReqDescription
destinationsarrayyes
modeTravel mode. Default: stored preference, else walk.
originsarrayyes

Structured output declared, but exposes no named fields.

No examples provided.

verify_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…

NameTypeReqDescription
claimsarrayyes
latnumberyes
lonnumberyes

Structured output declared, but exposes no named fields.

No examples provided.

warmup_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.

NameTypeReqDescription
city
confirmboolean
lat
lon
radius_mnumber

Structured output declared, but exposes no named fields.

No examples provided.

water_near ~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.

NameTypeReqDescription
latnumberyes
limitinteger
lonnumberyes
radius_mnumber
subtype
water_class

Structured output declared, but exposes no named fields.

No examples provided.

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.

NameTypeReqDescription
category
lat
lon
max_distance_mnumberyes
name
where

Structured output declared, but exposes no named fields.

No examples provided.

Common questions

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.