Endpoints: 28,729MCP servers: 18,413Payout addresses: 2,071Paid calls: 1,533Letters: 13Defects: 1,322counted just now
teppi

Server definition

Hash
sha256:a27531f21cebc40bf64379b758b32f4fa1c72c0f6a3841dbc0cced0e19c8cfbc
What it is
What a remote MCP server returned when asked what it offers: 49 tools

The blob, as servednamed by its sha256

{ "instructions": "MapMap: a self-hosted multi-modal routing platform — cars, trucks (with first-class ADR dangerous-goods support), bicycles, pedestrians and motor scooters. Tools: `route` computes turn-by-turn routes (costing \"auto\", \"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\" — pass a `truck` profile with dimensions and the ADR declaration to apply dimensional and hazmat restrictions to the search (see `applied_adr.tunnel_enforcement` for what a returned route does and does not prove), `pedestrian` options for lit-street preference, wheelchair/blind profiles and hiking difficulty, or `bicycle` options for bicycle type and road/surface/hill preferences; every one of these costings accepts `rationale: true` for an honest why-this-route explanation derived by route divergence); `search_along_route` finds POIs along a route with each one's real engine-computed detour cost (\"coffee, adds 4 min\"); `cheapest_fuel_along_route` ranks fuel stations along a route cheapest-first from live open-data price feeds (UK, FR, DE) with engine-computed detours, a 24-hour staleness flag (a stale price ranks below every fresh one and claims no saving) and the saving vs the cheapest on-route baseline; `cheapest_charging_along_route` ranks EV charge points along a route most-powerful-first from operator-published charge-point data, with engine-computed detours, connector and minimum-kW filters, and a coverage statement naming the operators covered — show it, because an empty result means \"none from these operators\", never \"no chargers here\"; `plan_ev_route` plans a whole EV journey — physics consumption over the route's legs, charge stops chosen so the state of charge never breaches the reserve floor, and charge times integrated over the vehicle's own charging curve rather than energy divided by peak power — and answers `feasible: false` with a named cause and the furthest reachable point rather than inventing a plan; `nearby_places` finds places near a point by category — cafes, fuel, EV charging, parking, pharmacies — and/or by name or brand (\"the nearest Lloyds bank\"), nearest first with distance in metres; both `nearby_places` and `reverse_geocode` can answer egocentrically — pass `heading_deg` (and optionally `fov_deg`) and every result carries a `direction` phrased from where the user is standing (\"ahead and slightly to your right, about 80 metres\"), which is what a voice or wearable client should read out instead of coordinates; `route` also takes `landmarks: true` for turn instructions anchored to recognisable places (\"Turn right just after the Shell garage\") alongside the engine's own, which is what a driver or a voice client actually navigates by; `plan_day` turns an itinerary (names or coordinates, dwell times, optional optimisation) into one navigable multi-stop route with per-stop ETAs; `check_adr_tunnel` answers whether a dangerous-goods load may pass a tunnel of a given ADR category (pure ADR 8.6.4 lookup, no network, conservative worst-case reading); `check_clearance_on_route` measures a vehicle's overhead clearance along a route against surveyed point cloud geometry, returning pass, fail, indeterminate or no_verdict with the limiting point, its measured headroom, the uncertainty bound on it and a link to that view. Read the honesty carefully on that one: it measures physical geometry from a dated survey, it is never a signed or posted height, and ground the survey did not cover comes back as `not_surveyed_m` and is never judged, so a pass is possible over complete coverage and nowhere else. It is the measured counterpart to the routing answer, which avoids what the map has tagged and nothing more, so reach for it whenever an unchanged truck route is about to be read as a clearance (the mapmap://guide/clearance resource sets out why those are two different claims); `match_trace` snaps a recorded GPS trace (dashcam, telematics, a walked track) to the road network and reports what it travelled over: the matched geometry, distance and time, then distance by road class, administrative area and surface with toll, bridge and tunnel totals; `list_place_categories` publishes the canonical category vocabulary the place tools accept, with the colloquial aliases for each — read it rather than guessing a category, since an unknown one matches nothing; `get_usage` reports what the calling key has spent in weighted quota UNITS (never a count of calls) by day and by endpoint, against its monthly allowance and prepaid balance, so an agent can check its own spend mid-task; `geocode` turns place names into coordinates; `reverse_geocode` turns coordinates into the nearest places (addresses, POIs and localities, nearest first, with distance in metres and POI opening hours/contact details where known); `matrix` computes many-to-many travel time/distance matrices in any costing; `reachable_area` computes isochrones — walkability/cyclability rings — as GeoJSON; `optimise_routes` solves multi-vehicle, multi-stop route optimisation (VRP: vehicles with capacities/skills/time windows, jobs and pickup-delivery shipments) — the travel-time matrix is computed by our routing engine, so passing costing \"truck\" with a `truck` profile makes the whole plan respect dimensional and ADR dangerous-goods restrictions, and it also takes fleet territories, multi-trip reload runs and a one-shot constraint relaxation, each reported in a block that states plainly what it approximated or gave away; `cluster` groups stops into balanced rounds so a day too large for one optimisation can be optimised a round at a time — straight-line, never road-network, and it says so in every answer, so use it to decide which stops belong together and `optimise_routes` to decide the order; `replan_routes` re-plans a shift part-way through — send the day back, because nothing is stored between calls, and the stops already served are locked because they already happened; `submit_optimise_job` and `get_job` are the asynchronous lane for a problem too large to answer inside one request (ten times the locations, four times the matrix elements), and polling `get_job` is free, so poll it rather than rationing checks. Map styling (MapMap Studio): `list_style_layers` lists the first-party named bases a style can start from plus the themable palette slots, skeleton layer ids and source-layers (local, always works); `create_style` publishes a new hosted style and takes a named `base` so a drawn style is one call rather than thirty palette entries; `get_style` fetches a style's theme and latest style URL; `set_palette` and `set_layer_paint` publish a new immutable style version with recoloured palette slots or a changed layer paint property. Attribution is enforced on every compiled style. Feedback: `report_map_issue` queues a first-party map/live-world mismatch observation for human review (never edits the map); `submit_integration_retro` sends MapMap a structured integration retro — only the schema's structured fields, never conversation or code — to be called at most once, after an integration works or is abandoned, and only if the developer has approved sending feedback. Coordinates are WGS84 decimal degrees; distances are metres and durations seconds unless a field name says otherwise.", "tools": [ { "description": "Road incidents a TRAFFIC CAMERA SAW and a vision model CONFIRMED, in force now: near a point (`near` [lat, lon] with `radius_m`, default 1000, at most 10000), in a `bbox` [min_lon, min_lat, max_lon, max_lat], or along a route `shape` (polyline6 from `route`, corridor `buffer_m` default 60). Give exactly one. Each record says what it asserts (`closed` or a `speed_override` in km/h) on which OSM ways and carriageway, what was seen (collision, stalled vehicle, wrong-way vehicle), the camera, the clip URL the verdict was made on (`evidence_url`), the verifier and its `confidence`, and when it lifts (`end`, always within minutes: these records expire on their own). Camera records RANK BELOW official closures and never retract one. READ `coverage_note` ALOUD: an empty list means no camera-verified incident here, NEVER a clear road. Show `attribution` verbatim (Powered by TfL Open Data). Off unless the deployment arms camera incidents (SN_CAMERA_INCIDENTS_ENABLED): an unarmed gateway answers 501 and says so. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "bbox": { "description": "`[min_lon, min_lat, max_lon, max_lat]`.", "items": { "format": "double", "type": "number" }, "maxItems": 4, "minItems": 4, "type": [ "array", "null" ] }, "buffer_m": { "description": "Corridor half-width for `shape`, metres.", "format": "double", "type": [ "number", "null" ] }, "include_recently_expired": { "default": false, "description": "Include records that have expired in the last hour, for the\noperator's timeline (default false).", "type": "boolean" }, "min_confidence": { "description": "Drop records below this confidence (default 0).", "format": "double", "type": [ "number", "null" ] }, "near": { "description": "`[lat, lon]` plus `radius_m` (default 1000).", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": [ "array", "null" ] }, "radius_m": { "description": "Radius for `near`, metres, at most 10000.", "format": "double", "type": [ "number", "null" ] }, "shape": { "description": "A route shape as polyline6; the corridor rule of\n`/v1/incidents/along` applies (`buffer_m` default 60).", "type": [ "string", "null" ] } }, "type": "object" }, "name": "camera_incidents", "outputSchema": { "$defs": { "CameraIncidentOut": { "description": "One camera-verified incident in force.", "properties": { "arc_m": { "description": "Distance along the route, metres, when queried by `shape`.", "format": "double", "type": [ "number", "null" ] }, "camera_name": { "description": "Camera name.", "type": "string" }, "camera_position": { "description": "Camera position `[lat, lon]`.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "confidence": { "description": "Verifier confidence, `0..=1`.", "format": "double", "type": "number" }, "end": { "description": "Lifts at, RFC 3339.", "type": "string" }, "evidence_url": { "description": "The clip the decision was made on.", "type": [ "string", "null" ] }, "id": { "description": "Override record id.", "type": "string" }, "incident_kind": { "$ref": "#/$defs/IncidentKind", "description": "What was seen." }, "kind": { "description": "`closed` or `speed_override`.", "type": "string" }, "source": { "description": "`cam-<provider>-<camera id>`.", "type": "string" }, "speed_kph": { "description": "Speed in force, km/h, for `speed_override`.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "start": { "description": "In force from, RFC 3339.", "type": "string" }, "verifier": { "description": "Which verifier answered.", "type": "string" }, "ways": { "description": "Roads asserted.", "items": { "$ref": "#/$defs/WayOut" }, "type": "array" } }, "required": [ "id", "source", "kind", "incident_kind", "camera_name", "camera_position", "ways", "start", "end", "confidence", "verifier" ], "type": "object" }, "Direction": { "description": "Carriageway of a way, spelled as the artefact spells it. A serde\ntwin of [`WayDirection`], which deliberately carries no serde.", "oneOf": [ { "const": "forward", "description": "Along the way's node order.", "type": "string" }, { "const": "backward", "description": "Against it.", "type": "string" }, { "const": "both", "description": "Both carriageways.", "type": "string" } ] }, "IncidentKind": { "description": "What kind of road event an incident describes, as far as a closure\ndecision cares.", "oneOf": [ { "const": "collision", "description": "Two or more vehicles made contact.", "type": "string" }, { "const": "stalled-vehicle", "description": "A vehicle stopped where it should not have.", "type": "string" }, { "const": "wrong-way", "description": "A vehicle travelling against the carriageway direction.", "type": "string" }, { "additionalProperties": false, "description": "Anything else, carrying the raw category for logs.", "properties": { "other": { "type": "string" } }, "required": [ "other" ], "type": "object" } ] }, "WayOut": { "description": "One road asserted.", "properties": { "direction": { "$ref": "#/$defs/Direction", "description": "Carriageway." }, "name": { "description": "Road name, when known.", "type": [ "string", "null" ] }, "way_id": { "description": "OSM way id.", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "way_id", "direction" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "attribution": { "description": "Attribution lines required by the camera providers, verbatim.", "items": { "type": "string" }, "type": "array" }, "coverage_note": { "description": "Plain-language statement of which cameras are covered and that\ncamera records rank below official closures. Agents must read it\naloud: an empty list means \"no camera-verified incident\", never\n\"road clear\".", "type": "string" }, "incidents": { "description": "Records in force (and recently expired, if asked).", "items": { "$ref": "#/$defs/CameraIncidentOut" }, "type": "array" } }, "required": [ "incidents", "coverage_note", "attribution" ], "type": "object" } }, { "description": "Find the best EV charge points along a route, with the REAL extra travel time of stopping at each one — never a straight-line guess. Provide `origin` + `destination` (a route is computed) or an existing route's `geometry_polyline6`, plus optional `connectors` (\"ccs\", \"type2\", \"chademo\", \"type1\", \"tesla\", \"domestic\", \"other\"), `min_kw` (e.g. 50 for rapid only), `available_only` and `max_detour_minutes` (default 10). Charge points come from operator-published feeds, are costed through the routing engine with your costing (a `truck` profile makes detours respect dimensional/ADR restrictions) and ranked most powerful first, since minutes off the clock are bought with kilowatts. Each result carries max_power_kw, connector_standards, best_connector, evse_count, detour_minutes/detour_km and, where a live feed backs it, available_now. IMPORTANT: there is no national charge-point registry — every deployment covers only the operators it has onboarded, so ALWAYS show the returned `coverage_note` alongside the results. An empty `results` means \"none from these operators within the detour budget\", NEVER \"there are no chargers here\". Statuses are live only when availability_live is true; otherwise they are the values captured at the last ingest and must not be described as current. `say` is ALWAYS present and is the whole answer as one short spoken line, composed by the gateway with the coverage already inside the claim rather than appended to it: it scopes its superlative to the operators this deployment holds, and an empty result's line says the emptiness is about those operators. Prefer reading it verbatim to writing your own summary. Requires the MapMap gateway; answers a clear error when the deployment has no charge-point dataset. Display the returned charging_attribution with the results.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "available_only": { "description": "Keep only charge points with a bay reported free right now. Needs\nthe deployment to have a live availability feed; without one the\ncall is refused rather than silently returning nothing.", "type": [ "boolean", "null" ] }, "connectors": { "description": "Keep only charge points offering at least one of these connector\nstandards: \"type2\", \"type1\", \"ccs\", \"chademo\", \"tesla\", \"domestic\"\nor \"other\". Omitted ⇒ every standard.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model for the route and detour matrix: \"auto\" (default),\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\"." }, "destination": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route destination." }, "geometry_polyline6": { "description": "An existing route geometry as an encoded polyline6 (the `route`\ntool's `geometry_polyline6`). Provide either this or `origin` +\n`destination`, not both.", "type": [ "string", "null" ] }, "max_detour_minutes": { "description": "Largest acceptable detour in minutes (default 10, at most 120).", "format": "double", "type": [ "number", "null" ] }, "max_results": { "description": "Maximum results (default 5, at most 25).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "min_kw": { "description": "Keep only charge points whose best connector is rated at least this\nmany kW (e.g. 50 for rapid charging only).", "format": "double", "type": [ "number", "null" ] }, "origin": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route origin (with `destination`, when no geometry is given)." }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; the detours then respect dimensional/ADR restrictions." } }, "type": "object" }, "name": "cheapest_charging_along_route", "outputSchema": { "$defs": { "ChargerConnector": { "description": "The highest-rated connector at a charge point.", "properties": { "dc": { "description": "Whether this connector delivers DC (rapid) rather than AC.", "type": "boolean" }, "power_kw": { "description": "Rated power in kW, where the operator publishes enough to know it.", "format": "double", "type": [ "number", "null" ] }, "power_kw_source": { "description": "\"declared\" when the operator published the rating, \"derived\" when\nit was computed from voltage × amperage × phases. Never present\nthe two as the same thing to a user.", "type": [ "string", "null" ] }, "standard": { "description": "Normalised standard: \"type2\", \"type1\", \"ccs\", \"chademo\", \"tesla\",\n\"domestic\" or \"other\".", "type": "string" } }, "required": [ "standard", "dc" ], "type": "object" }, "ChargerHit": { "description": "One charge point along the route, with its real engine-computed detour.", "properties": { "along_route_position": { "description": "How far along the route the charge point sits, 0.0–1.0.", "format": "double", "type": "number" }, "available_now": { "description": "Whether a bay is free right now. Present only where a live\navailability feed backs the claim — absent means unknown, never\n\"occupied\".", "type": [ "boolean", "null" ] }, "best_connector": { "anyOf": [ { "$ref": "#/$defs/ChargerConnector" }, { "type": "null" } ], "description": "The highest-rated connector at the site." }, "charger_id": { "description": "Operator-scoped stable id.", "type": "string" }, "connector_standards": { "description": "Every distinct connector standard at the site.", "items": { "type": "string" }, "type": "array" }, "detour_km": { "description": "Extra distance of the detour in kilometres, when the engine\nreported distances.", "format": "double", "type": [ "number", "null" ] }, "detour_minutes": { "description": "Real extra travel time of stopping here, minutes.", "format": "double", "type": "number" }, "detour_s": { "description": "The same detour in seconds.", "format": "double", "type": "number" }, "evse_count": { "description": "Number of charging positions (EVSEs) at the site.", "format": "uint", "minimum": 0, "type": "integer" }, "lat": { "description": "WGS84 latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "WGS84 longitude in decimal degrees.", "format": "double", "type": "number" }, "max_power_kw": { "description": "Highest rated power at the site, kW.", "format": "double", "type": [ "number", "null" ] }, "name": { "description": "Site name, where the operator publishes one.", "type": [ "string", "null" ] }, "off_route_m": { "description": "Straight-line offset from the route geometry, metres.", "format": "double", "type": "number" }, "operator": { "description": "Operator display name.", "type": [ "string", "null" ] }, "source": { "description": "Which operator feed this came from.", "type": "string" }, "status_live": { "description": "Whether the status came from a live feed rather than the last\ningest.", "type": "boolean" }, "updated_at": { "description": "When the operator last updated this record.", "type": [ "string", "null" ] } }, "required": [ "charger_id", "source", "lat", "lon", "connector_standards", "evse_count", "status_live", "detour_minutes", "detour_s", "along_route_position", "off_route_m" ], "type": "object" }, "ChargingSource": { "description": "One operator's coverage and licence, as published by the deployment.", "properties": { "chargers": { "description": "How many charge points this operator contributes.", "format": "uint", "minimum": 0, "type": "integer" }, "coverage_note": { "description": "Plain-language statement of what this operator's feed does and\ndoes not cover.", "type": [ "string", "null" ] }, "licence": { "description": "Licence or statutory basis of the feed.", "type": [ "string", "null" ] }, "operator": { "description": "Operator display name.", "type": "string" }, "source_id": { "description": "Operator id.", "type": "string" } }, "required": [ "source_id", "operator", "chargers" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "availability_live": { "description": "Whether statuses are live (a bring-your-own availability feed) or\nthe values captured at the last ingest.", "type": "boolean" }, "availability_note": { "description": "Plain-language explanation of what the statuses mean here.", "type": "string" }, "candidate_cap": { "description": "The matrix fan-out cap in force.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_considered": { "description": "Charge points matching the filters that passed the corridor\npre-filter.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_costed": { "description": "Candidates actually costed through the engine (fan-out capped at\n`candidate_cap`, most powerful kept).", "format": "uint", "minimum": 0, "type": "integer" }, "charging_attribution": { "description": "Attribution string for the charge-point operators actually\nreturned — display it with the results (a licence obligation).", "type": [ "string", "null" ] }, "costing": { "description": "The costing the detours were computed with.", "type": "string" }, "coverage_note": { "description": "**Always present.** What this deployment's charge-point dataset\ndoes and does not cover. An empty `results` means \"none from these\noperators within the budget\" — never \"there are no chargers here\".\nShow this to the user alongside the results.", "type": "string" }, "max_detour_minutes": { "description": "The detour budget applied, minutes.", "format": "double", "type": "number" }, "note": { "description": "Why `results` is empty, when it is — the cause, not a bare list.", "type": [ "string", "null" ] }, "results": { "description": "Charge points within the detour budget, most powerful first\n(power, then detour).", "items": { "$ref": "#/$defs/ChargerHit" }, "type": "array" }, "route_distance_m": { "description": "Direct origin→destination distance in metres.", "format": "double", "type": [ "number", "null" ] }, "route_duration_s": { "description": "Direct origin→destination travel time in seconds (same estimator\nas the detour legs), when routable.", "format": "double", "type": [ "number", "null" ] }, "route_length_m": { "description": "Length of the route geometry in metres.", "format": "double", "type": "number" }, "say": { "description": "**Always present.** The whole answer as one short spoken line,\ncomposed by the gateway. Coverage is inside the claim rather than\nappended to it: the line scopes its superlative to the operators\nthis deployment holds, and an empty result's line says the\nemptiness is about those operators, never about the road. Safe to\nread to a driver verbatim.", "type": "string" }, "sources": { "description": "Every operator in the dataset, with its own coverage note and\nlicence. Present even when `results` is empty.", "items": { "$ref": "#/$defs/ChargingSource" }, "type": "array" } }, "required": [ "costing", "route_length_m", "candidates_considered", "candidates_costed", "candidate_cap", "max_detour_minutes", "results", "coverage_note", "sources", "availability_live", "availability_note", "say" ], "type": "object" } }, { "description": "Find the cheapest fuel along a route, with the REAL extra travel time of stopping at each station — never a straight-line guess. Provide `origin` + `destination` (a route is computed) or an existing route's `geometry_polyline6`, plus a `fuel` code (\"diesel\" default, \"petrol_95\", \"petrol_98\", \"premium_diesel\", \"e85\", \"lpg\") and `max_detour_minutes` (default 10). Stations come from the live open-data price feeds (UK CMA retailer scheme and/or the statutory Fuel Finder, FR prix-carburants, DE Tankerkoenig; the response's `fuel_attribution` names the ones actually matched), are priced through the routing engine with your costing (a `truck` profile makes detours respect dimensional/ADR restrictions) and ranked freshest-priced first and cheapest within that. Each result carries price {value, currency, updated_at, stale}, detour_minutes/detour_km, and saving_per_litre vs the cheapest on-route baseline (pass `fill_litres` to also get saving_total). `stale` = not verifiably fresher than 24 h: true unless BOTH the price's own updated_at and the snapshot's fetch time are inside that window, and true whenever either is missing. A stale price is ranked below every fresh one, is never the baseline, and carries NO saving_per_litre — quote it as \"last seen at X on <date>\", never as a saving. `say` is ALWAYS present and is the whole answer as one short spoken line, composed by the gateway with the number, the detour and the honest qualifier already in it. Prefer reading it verbatim to writing your own summary: a stale price's line states the figure and the day it was last seen and claims no saving, and an empty result's line names the cause rather than implying there is no fuel on that road. Requires the MapMap gateway; answers a clear error when the deployment has no fuel-price dataset. Display the returned fuel_attribution with the prices.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model for the route and detour matrix: \"auto\" (default),\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\"." }, "destination": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route destination." }, "fill_litres": { "description": "Optional fill size in litres; each result then also carries\n`saving_total` = `saving_per_litre` × `fill_litres`.", "format": "double", "type": [ "number", "null" ] }, "fuel": { "description": "Which fuel to price: \"diesel\" (default), \"petrol_95\", \"petrol_98\",\n\"premium_diesel\", \"e85\" or \"lpg\" (aliases \"petrol\", \"unleaded\",\n\"e10\", \"super_unleaded\", \"e5\", \"b7\" and \"sdv\" are accepted).", "type": [ "string", "null" ] }, "geometry_polyline6": { "description": "An existing route geometry as an encoded polyline6 (the `route`\ntool's `geometry_polyline6`). Provide either this or `origin` +\n`destination`, not both.", "type": [ "string", "null" ] }, "max_detour_minutes": { "description": "Largest acceptable detour in minutes (default 10, at most 120).", "format": "double", "type": [ "number", "null" ] }, "max_results": { "description": "Maximum results (default 5, at most 25).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "origin": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route origin (with `destination`, when no geometry is given)." }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; the detours then respect dimensional/ADR restrictions." } }, "type": "object" }, "name": "cheapest_fuel_along_route", "outputSchema": { "$defs": { "FuelBaseline": { "description": "The cheapest effectively-on-route option in one currency — what the\ndriver pays by just pulling in without a detour; the reference the\nper-result savings are computed against. Only a fresh price is\neligible, so this is `stale: false` by construction.", "properties": { "brand": { "description": "The baseline station's brand, when known.", "type": [ "string", "null" ] }, "currency": { "description": "ISO 4217 currency this baseline covers.", "type": "string" }, "name": { "description": "The baseline station's human label, when known.", "type": [ "string", "null" ] }, "stale": { "description": "True when the baseline price could not be verified fresher than\n24 hours.", "type": "boolean" }, "station_id": { "description": "The baseline station's id.", "type": "string" }, "updated_at": { "description": "The baseline price's source timestamp, verbatim.", "type": "string" }, "value": { "description": "Baseline price per litre.", "format": "double", "type": "number" } }, "required": [ "currency", "value", "station_id", "updated_at", "stale" ], "type": "object" }, "FuelPriceQuote": { "description": "One live pump price: per-litre value in an ISO 4217 currency, with the\nsource's own update timestamp and a 24-hour staleness flag.", "properties": { "currency": { "description": "ISO 4217 code (GBP for the UK sources, EUR for FR/DE).", "type": "string" }, "stale": { "description": "True when the price could not be verified fresher than 24 hours:\nunless BOTH the source's own `updated_at` and the snapshot's fetch\ntime fall inside that window, and always when either is missing.\nTreat it as indicative, not bindable — say \"last seen at X on\n<date>\", never quote it as today's price or as a saving.", "type": "boolean" }, "updated_at": { "description": "The source's own price timestamp, verbatim.", "type": "string" }, "value": { "description": "Price per litre in `currency`.", "format": "double", "type": "number" } }, "required": [ "value", "currency", "updated_at", "stale" ], "type": "object" }, "FuelStationHit": { "description": "One fuel station along the route, priced with its honest detour.", "properties": { "along_route_position": { "description": "Where along the route the station sits, 0.0 (origin) to 1.0\n(destination).", "format": "double", "type": "number" }, "brand": { "description": "Brand where the feed carries one.", "type": [ "string", "null" ] }, "detour_km": { "description": "Extra travel distance in kilometres, when the engine reported\ndistances.", "format": "double", "type": [ "number", "null" ] }, "detour_minutes": { "description": "Extra travel time of refuelling here, in minutes (rounded to 0.1):\n(origin→station) + (station→destination) − (origin→destination),\nall computed by the routing engine — never a straight-line guess.", "format": "double", "type": "number" }, "detour_s": { "description": "The same detour in raw seconds.", "format": "double", "type": "number" }, "lat": { "description": "WGS84 latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "WGS84 longitude.", "format": "double", "type": "number" }, "name": { "description": "Human label: trading name, town or address, where the feed\ncarries one.", "type": [ "string", "null" ] }, "off_route_m": { "description": "Straight-line distance from the station to the route, metres.", "format": "double", "type": "number" }, "price": { "$ref": "#/$defs/FuelPriceQuote", "description": "The requested fuel's live pump price at this station." }, "saving_per_litre": { "description": "Per-litre saving against the cheapest effectively-on-route station\nin the same currency (negative = dearer than staying on route).\nAbsent when no FRESH station sits on the route itself in this\ncurrency, and always absent on a stale result: an unverifiable\nprice may state a number but never claim a saving.", "format": "double", "type": [ "number", "null" ] }, "saving_total": { "description": "`saving_per_litre` × the request's `fill_litres`, when both exist.", "format": "double", "type": [ "number", "null" ] }, "source": { "description": "Which national feed the station came from (\"uk-fuelfinder\", \"uk\",\n\"fr\", \"de\").", "type": "string" }, "station_id": { "description": "Source-scoped stable station id.", "type": "string" } }, "required": [ "station_id", "source", "lat", "lon", "price", "detour_minutes", "detour_s", "along_route_position", "off_route_m" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "baseline": { "description": "The cheapest effectively-on-route option per currency (empty when\nno station sits on the route itself).", "items": { "$ref": "#/$defs/FuelBaseline" }, "type": "array" }, "candidate_cap": { "description": "The matrix fan-out cap in force.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_considered": { "description": "Stations selling the fuel that passed the corridor pre-filter.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_costed": { "description": "Candidates actually priced through the engine (fan-out capped at\n`candidate_cap`, cheapest kept).", "format": "uint", "minimum": 0, "type": "integer" }, "costing": { "description": "The costing the detours were priced with.", "type": "string" }, "fuel": { "description": "The normalised fuel code that was priced (e.g. \"diesel\").", "type": "string" }, "fuel_attribution": { "description": "Attribution string for the fuel-price data sources actually\nreturned — display it with the prices (a licence obligation).", "type": [ "string", "null" ] }, "max_detour_minutes": { "description": "The detour budget applied, minutes.", "format": "double", "type": "number" }, "results": { "description": "Stations within the detour budget, cheapest first (price, then\ndetour).", "items": { "$ref": "#/$defs/FuelStationHit" }, "type": "array" }, "route_distance_m": { "description": "Direct origin→destination distance in metres.", "format": "double", "type": [ "number", "null" ] }, "route_duration_s": { "description": "Direct origin→destination travel time in seconds (same estimator\nas the detour legs), when routable.", "format": "double", "type": [ "number", "null" ] }, "route_length_m": { "description": "Length of the route geometry in metres.", "format": "double", "type": "number" }, "say": { "description": "**Always present.** The whole answer as one short spoken line,\ncomposed by the gateway: the number, the detour, and the honest\nqualifier where one applies. Safe to read to a driver verbatim,\nand safe on an empty result, where it names the cause rather than\nimplying there is no fuel on the road. A stale price's line states\nthe figure and the day it was last seen and claims no saving.", "type": "string" } }, "required": [ "fuel", "costing", "route_length_m", "candidates_considered", "candidates_costed", "candidate_cap", "max_detour_minutes", "baseline", "results", "say" ], "type": "object" } }, { "description": "Check whether a vehicle may pass through a tunnel of a given ADR category (\"A\"–\"E\"). Provide `hazmat` and, when known, the load's ADR 8.6.4 tunnel restriction code (e.g. \"B\", \"C5000D\", \"B/D\", \"none\"). Applies the conservative worst-case reading: conditional clauses are assumed to apply, so a blocked answer may over-restrict but never under-restricts. No network access; answers instantly.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "hazmat": { "description": "Whether the vehicle carries dangerous goods at all. When false the\nADR tunnel matrix does not apply and every tunnel is permitted.", "type": "boolean" }, "tunnel_category": { "description": "ADR category of the tunnel to check: \"A\", \"B\", \"C\", \"D\" or \"E\".", "type": "string" }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\". Leave unset for a hazmat load of unknown\ncode (conservatively treated as code B).", "type": [ "string", "null" ] } }, "required": [ "hazmat", "tunnel_category" ], "type": "object" }, "name": "check_adr_tunnel", "outputSchema": { "$defs": { "DecisionStatus": { "description": "The tunnel-entry decision, mirroring `sn_adr::Decision`.", "oneOf": [ { "const": "allowed", "description": "Passage is permitted.", "type": "string" }, { "const": "blocked", "description": "Passage is forbidden.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "decision": { "$ref": "#/$defs/DecisionStatus", "description": "Whether passage is allowed or blocked." }, "explanation": { "description": "Human-readable explanation of how the decision was reached,\nincluding the conservative worst-case reading.", "type": "string" }, "forbidden_tunnel_categories": { "description": "ADR tunnel categories this load is forbidden from under the\nworst-case reading (conditional clauses assumed to apply). Empty\nwhen unrestricted.", "items": { "type": "string" }, "type": "array" }, "reason": { "description": "Why passage is forbidden (present only when blocked); cites the\nADR 8.6.4 rule that applied.", "type": [ "string", "null" ] } }, "required": [ "decision", "explanation", "forbidden_tunnel_categories" ], "type": "object" } }, { "description": "Measure a vehicle's overhead clearance along a route against surveyed point cloud geometry, wherever survey coverage exists. Routes with truck costing (so the search already avoids the height restrictions the map has tagged), then measures that corridor. Give `origin`, `destination` and `height_m`; optional `width_m` asks the corridor-width axis too, and optional `margin_m` adds your operating margin to the vehicle before the verdict. Returns `pass`, `fail`, `indeterminate` or `no_verdict` with the limiting point, the measured headroom, its uncertainty bound (`safe_headroom_m`, `sigma_m`, `sampling_gap_m`) and a link to that exact view in the survey viewer. An `indeterminate` carries `indeterminate_reasons` as codes to branch on and the same reasons as English inside `explanation`; read out the English. A `pass` may carry no limiting point at all, which means the survey found nothing above that corridor, and the width axis may answer `not_assessed` where the corridor edges are too sparsely surveyed while the height axis still answers. Honesty, and it matters here: this measures physical geometry from a dated survey. It is not a signed or posted height, `clearance_enforcement.route_certified` is always false, and the caveat is on every answer including the clear one. Ground the survey did not cover comes back as `not_surveyed_m` and is never judged, so a `pass` is possible over complete coverage and nowhere else; sparse or stale coverage comes back separately as `insufficient_data_m`. Reach for this when a truck route came back unchanged and you need to know whether that means anything: an unchanged route avoids what the map records, which is a different claim from measured headroom, because a structure nobody tagged is routed through like open road. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), which holds the surveys; there is no fallback, and it will not answer from the routing step alone. The mapmap://guide/clearance resource sets out what each answer proves.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "destination": { "$ref": "#/$defs/LatLon", "description": "Route destination." }, "height_m": { "description": "Vehicle height in metres. Required: there is no default vehicle,\nbecause a default vehicle is how somebody gets an answer about a\nlorry that is not theirs.", "format": "double", "type": "number" }, "margin_m": { "description": "Operating margin in metres, added to the height before the verdict\nis decided (default 0). Your compliance policy, not ours: the\nmeasured figure and the safe bound are both reported whatever you\nset here, and the margin is echoed back. It is applied to the\nmeasurement, not to the routing step, where the map's own posted\nheights already carry a margin of their own.", "format": "double", "type": [ "number", "null" ] }, "origin": { "$ref": "#/$defs/LatLon", "description": "Route origin." }, "width_m": { "description": "Vehicle width in metres. Supplying it asks the width axis as well as\nthe height axis; the width answer is reported separately and is\nnever a headroom.", "format": "double", "type": [ "number", "null" ] } }, "required": [ "origin", "destination", "height_m" ], "type": "object" }, "name": "check_clearance_on_route", "outputSchema": { "$defs": { "ClearanceDatasetSummary": { "description": "One survey the report drew on.", "properties": { "dataset": { "description": "The dataset id.", "type": "string" }, "stale": { "description": "Whether the clearance field lags the survey it was baked from, or\nhas never been checked against it. Either way no route passes on it.", "type": "boolean" }, "survey_dates": { "description": "Capture range, `from/to` in ISO dates.", "type": "string" } }, "required": [ "dataset", "survey_dates", "stale" ], "type": "object" }, "ClearanceEnforcement": { "description": "How a clearance answer was arrived at, and what it therefore does and\ndoes not prove.\n\nThe [`TunnelEnforcement`] pattern, applied to the other safety-adjacent\nanswer this server gives, and for the same reason: a headroom figure\nwith a bound on it reads like permission. It is not one. The block is\ncarried on every response including the clear one, because the clear one\nis the dangerous one.\n\nEvery field is taken verbatim from the gateway's report rather than\nrebuilt here. `vertical_datum` is a property of the survey the figures\ncame from, so this server cannot know it; and copying the caveat text\ninto a second crate is exactly the drift the single-source-of-truth rule\nexists to prevent. When the gateway omits any part of it, the tool\nrefuses the answer rather than emitting a report with a hollow caveat.", "properties": { "basis": { "description": "How the figures were arrived at. Always\n`\"surveyed_pointcloud_within_survey_difference\"`: the difference\nbetween two heights measured in the same survey, on the same date,\nby the same processing. No map tag, no terrain model and no absolute\nvertical datum enters it.", "type": "string" }, "caveat": { "description": "What this answer does and does not prove, in one paragraph.", "type": "string" }, "route_certified": { "description": "Whether the route has been certified against a structure inventory.\nAlways `false`, and there is no future in which it is true: this is\na measurement with a bound, never a certificate about a route.", "type": "boolean" }, "vertical_datum": { "description": "The vertical frame the surveyed figures live in, spelled for a\nreader.", "type": "string" } }, "required": [ "basis", "route_certified", "vertical_datum", "caveat" ], "type": "object" }, "ClearanceWidthSummary": { "description": "The width axis: how wide the clear corridor is, never how tall.\n\nA separate type from [`LimitingPointSummary`] on purpose, with no\nheadroom field on it. These are different physical quantities, and a\nshared type is how the reader of a width answer ends up quoting a\nheadroom.", "properties": { "clear_width_m": { "description": "Free width of the narrowest clear corridor found, metres. Present\nwhere a corridor edge was actually measured.", "format": "double", "type": [ "number", "null" ] }, "reason": { "description": "Why the width axis declined to answer, present for\n`not_assessed`.", "type": [ "string", "null" ] }, "safe_clear_width_m": { "description": "That width less its uncertainty terms, metres: the number the width\nverdict is decided on.", "format": "double", "type": [ "number", "null" ] }, "verdict": { "description": "`pass`, `fail`, `indeterminate`, `no_verdict`, or `not_assessed`\nwhere the geometry or the request did not support an answer.", "type": "string" } }, "required": [ "verdict" ], "type": "object" }, "LimitingPointSummary": { "description": "One measured clearance on the route, with the bounds that qualify it.\n\nThere is no field here for a posted, signed or otherwise legally\nbinding height, and that absence is deliberate. A survey measures the\nphysical gap; a sign records a restriction a road authority posted, set\nbelow the physical gap on purpose. They are different quantities and\nthis server never reports one as the other.", "properties": { "dataset": { "description": "The dataset the measurement came from.", "type": "string" }, "headroom_m": { "description": "Measured headroom: the surveyed gap between the road surface and\nthe lowest validated surface above it, metres.", "format": "double", "type": "number" }, "lat": { "description": "Latitude of the point, decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude of the point, decimal degrees.", "format": "double", "type": "number" }, "overhead_class": { "description": "What the overhead surface is made of: `structure`, `vegetation`,\n`wire` or `unknown`. Foliage is compressible and seasonal, so an old\nreading of it stops deciding.", "type": "string" }, "route_distance_m": { "description": "How far along the route the point sits, metres.", "format": "double", "type": "number" }, "safe_headroom_m": { "description": "`headroom_m` less the uncertainty terms, metres. This is the number\nthe verdict is decided on, and the number to plan against.", "format": "double", "type": "number" }, "sampling_gap_m": { "description": "One-sided sampling bias bound, metres: how far below the lowest\nsample the true low point could hang, given the sample spacing.", "format": "double", "type": "number" }, "sigma_m": { "description": "One-sigma measurement uncertainty on that figure, metres.", "format": "double", "type": "number" }, "surveyed_on": { "description": "Last capture date of the survey behind this measurement, ISO.", "type": "string" } }, "required": [ "lat", "lon", "route_distance_m", "headroom_m", "sigma_m", "sampling_gap_m", "safe_headroom_m", "overhead_class", "surveyed_on", "dataset" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "advisories": { "description": "Notes on stretches of the route: vegetation age, a reading limited\nby a wire, a seam between surveys.", "items": { "type": "string" }, "type": "array" }, "assessed_m": { "description": "Metres of route measured against survey data good enough to decide\non.", "format": "double", "type": "number" }, "basis": { "description": "Always `\"surveyed_geometry_not_signage\"`.", "type": "string" }, "clearance_enforcement": { "$ref": "#/$defs/ClearanceEnforcement", "description": "What this answer does and does not prove. Read it." }, "datasets": { "description": "The surveys drawn on, with their capture dates and staleness.", "items": { "$ref": "#/$defs/ClearanceDatasetSummary" }, "type": "array" }, "explanation": { "description": "What was measured, when, with what bound, and what the tool declined\nto conclude. It may over-restrict; it never under-restricts.", "type": "string" }, "geometry_polyline6": { "description": "The route as a six-digit-precision encoded polyline, so the same\nshape can be drawn or re-measured without routing again.", "type": "string" }, "indeterminate_reasons": { "description": "Why an `indeterminate` could not be called, as machine-readable\ncodes (`inside_uncertainty_band`, `vegetation_age_exceeded`,\n`artefact_stale`, `artefact_freshness_unchecked`). Empty on every\nother verdict. These are for branching on, not for reading out: the\nsame reasons appear as English in `explanation`, and a person shown\n`artefact_freshness_unchecked` has been failed by whatever displayed\nit.", "items": { "type": "string" }, "type": "array" }, "insufficient_data_m": { "description": "Metres of route a survey covers but too sparsely, or too stale, to\ndecide on. Also an absence, and reported apart from\n`not_surveyed_m` because the two have different remedies.", "format": "double", "type": "number" }, "limiting": { "anyOf": [ { "$ref": "#/$defs/LimitingPointSummary" }, { "type": "null" } ], "description": "The limiting point on a `fail`, the tightest point on a `pass`, the\nworst contested point on an `indeterminate`. Absent on a\n`no_verdict`, and absent on a `pass` where the survey found nothing\nat all above the corridor." }, "not_surveyed_m": { "description": "Metres of route no survey covers. This is the absence of a\nmeasurement, and it is never the same statement as a measured open\nsky. No verdict is drawn over it.", "format": "double", "type": "number" }, "resolution_hint": { "description": "What would resolve an `indeterminate`, in one sentence, as the\nmeasuring service phrased it.", "type": [ "string", "null" ] }, "route_distance_m": { "description": "Length of the route the vehicle was routed over, metres.", "format": "double", "type": "number" }, "route_duration_s": { "description": "Estimated driving time for that route, seconds.", "format": "double", "type": "number" }, "verdict": { "description": "`pass`, `fail`, `indeterminate` or `no_verdict` for the height axis.\nA `pass` is possible over completely surveyed ground and nowhere\nelse: unsurveyed ground yields `no_verdict`, never a pass.", "type": "string" }, "view_url": { "description": "Deep link to that exact view in the survey viewer, so the reading\ncan be looked at rather than taken on trust.", "type": [ "string", "null" ] }, "width": { "$ref": "#/$defs/ClearanceWidthSummary", "description": "The width axis, answered separately when `width_m` was given." } }, "required": [ "verdict", "basis", "clearance_enforcement", "route_distance_m", "route_duration_s", "geometry_polyline6", "assessed_m", "not_surveyed_m", "insufficient_data_m", "width", "datasets", "advisories", "indeterminate_reasons", "explanation" ], "type": "object" } }, { "description": "Audit a map style's colour contrast against WCAG 2.1 (4.5:1 for label text, 3:1 for graphics like the route line), across both the light and dark palette variants. Pass a hosted `style_id` OR an inline `theme` document (as accepted by create_style). Advisory: failing pairs list the palette slots to adjust with set_palette; publishing is never blocked on contrast.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "Hosted style id whose latest theme should be checked…", "type": [ "string", "null" ] }, "theme": { "description": "…or an inline theme document (as accepted by `create_style`).\nExactly one of `style_id`/`theme` must be given." } }, "type": "object" }, "name": "check_style_contrast", "outputSchema": { "$defs": { "ContrastFindingInfo": { "description": "One audited colour pair of a contrast report.", "properties": { "background": { "description": "Effective backing palette slot.", "type": "string" }, "description": { "description": "Why this pair matters cartographically.", "type": "string" }, "foreground": { "description": "Foreground palette slot.", "type": "string" }, "kind": { "description": "Pair kind: \"text\" (threshold 4.5) or \"graphics\" (threshold 3.0).", "type": "string" }, "passes": { "description": "Whether the pair meets its threshold.", "type": "boolean" }, "ratio": { "description": "Measured WCAG 2.1 contrast ratio.", "format": "double", "type": "number" }, "threshold": { "description": "The WCAG threshold applied.", "format": "double", "type": "number" }, "variant": { "description": "Palette variant audited: \"light\" or \"dark\".", "type": "string" } }, "required": [ "variant", "kind", "foreground", "background", "description", "ratio", "threshold", "passes" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "findings": { "description": "Every audited pair, failing pairs first.", "items": { "$ref": "#/$defs/ContrastFindingInfo" }, "type": "array" }, "passes": { "description": "True when every audited pair meets its WCAG threshold.", "type": "boolean" } }, "required": [ "passes", "findings" ], "type": "object" } }, { "description": "Group stops into balanced geographic clusters, so a day too large for one optimisation can be optimised one cluster at a time. This is the front half of the recipe for a thousand-stop day: cluster here, then call `optimise_routes` per cluster, where the routing engine's own matrix decides the visiting order. IMPORTANT — this is STRAIGHT-LINE clustering. Distances are measured between coordinates, not along the road network: no road, river, motorway junction or one-way system is consulted, and two stops either side of an estuary look adjacent. That makes it the right tool for deciding which stops belong TOGETHER and the wrong one for deciding what ORDER to visit them in. The answer carries a `basis` sentence saying exactly this; show it, so a centroid is never read as a plan. Provide `locations` ([{id, lat, lon, load?}], ids unique, at most 5,000) and EXACTLY ONE of `clusters` (how many groups, balanced by stop count), `max_cluster_locations` or `max_cluster_load` (a per-cluster ceiling the count is derived from). Optional `territories` keep a cluster from straddling a round: each is clustered on its own, and so are the stops inside none of them. Optional `seed` (default 42) drives the seeding — the same request with the same seed always returns the same clusters, on every deployment, so a re-run is a re-run. Returns each cluster's member ids, count, summed load, centroid and territory, plus a `balance` block naming the constraint applied and whether it had to be relaxed to place every stop: a load ceiling with lumpy loads is a bin-packing problem and may have no solution at the derived count. Requires the MapMap gateway.", "inputSchema": { "$defs": { "ClusterLocationSpec": { "description": "One stop to be clustered.", "properties": { "id": { "description": "Caller-chosen id, echoed back as the cluster's membership. Must be\nunique within the request.", "type": "string" }, "lat": { "description": "Latitude in decimal degrees.", "format": "double", "type": "number" }, "load": { "description": "Optional weight — parcels, kilograms, litres, minutes of service.\nSummed per cluster and reported; constrains the clustering only\nunder `max_cluster_load`.", "format": "double", "type": [ "number", "null" ] }, "lon": { "description": "Longitude in decimal degrees.", "format": "double", "type": "number" } }, "required": [ "id", "lat", "lon" ], "type": "object" }, "TerritorySpec": { "description": "One named territory: a polygon that bounds which vehicle may serve\nwhich stop.", "properties": { "id": { "description": "Caller-chosen id, echoed back and referenced by\n`vehicles[].territory_ids`. Must be unique within the request.", "type": "string" }, "polygon": { "description": "The outer ring as GeoJSON `[lon, lat]` positions — longitude\nFIRST. Closed or open; an unclosed ring is closed for you.", "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" } }, "required": [ "id", "polygon" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "clusters": { "description": "How many clusters to produce, balanced by stop count. Give exactly\none of `clusters`, `max_cluster_locations` or `max_cluster_load`.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "locations": { "description": "The stops to group. Ids must be unique; at most 5,000.", "items": { "$ref": "#/$defs/ClusterLocationSpec" }, "type": "array" }, "max_cluster_load": { "description": "At most this much summed `load` per cluster; the cluster count is\nderived from it. A load ceiling with lumpy loads is a bin-packing\nproblem and may have no solution at the derived count — the\nresponse says so rather than pretending.", "format": "double", "type": [ "number", "null" ] }, "max_cluster_locations": { "description": "At most this many stops per cluster; the cluster count is derived\nfrom it.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "seed": { "description": "Seed for the k-means++ seeding (default 42). The same request with\nthe same seed always returns the same clusters, on every\ndeployment.", "format": "uint64", "minimum": 0, "type": [ "integer", "null" ] }, "territories": { "description": "Optional territories. Given, no cluster straddles one: each\nterritory is clustered on its own, and so are the stops inside none\nof them.", "items": { "$ref": "#/$defs/TerritorySpec" }, "type": [ "array", "null" ] } }, "required": [ "locations" ], "type": "object" }, "name": "cluster", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "balance": { "description": "The constraint applied, the largest cluster produced, and whether\nthe ceiling had to be relaxed to place every stop." }, "basis": { "description": "The method statement: this is STRAIGHT-LINE clustering. Distances\nare between coordinates, not along roads — two stops either side of\nan estuary look adjacent. Show it. It is the sentence that stops a\ncentroid being read as a plan.", "type": "string" }, "clusters": { "description": "The clusters: each with its `id`, member `locations` (your ids),\n`count`, summed `load`, `centroid` and the `territory` it belongs\nto." }, "parameters": { "description": "The seed, the cluster count, the locations seen, how many\nterritories were used, the iterations run and whether it converged." } }, "required": [ "clusters", "parameters", "balance", "basis" ], "type": "object" } }, { "description": "Create a hosted map style. Provide a name, optionally a named `base` to start from (light, dark, streets, midnight, navigator-day, navigator-night, fleet, outdoor, dataviz, backdrop, print - call list_style_layers for what each one is for), and optionally a theme document ({base: \"light\"|\"dark\", palette: {slot: colour}, layers: {layer_id: overrides}}). The named base is laid down first and the theme document is merged over it, so \"streets with darker water\" is one call; with neither, the style starts from the default theme. A theme may also set worldview (ISO 3166-1 alpha-2, accepted: AE, KR, SA, US) to display that jurisdiction's official names for a small curated registry of renamed features; omitted, labels keep the OSM on-the-ground names. Returns the generated style_id (pass it to set_palette / set_layer_paint) and the compiled style URL for MapLibre.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "base": { "description": "Optional named base to start from, e.g. \"streets\", \"midnight\",\n\"navigator-night\", \"fleet\". Call `list_style_layers` for the\ncatalogue, including which bases expect an overlay of their own. The\nbase's whole document is laid down first and `theme` is merged over\nit, so a base plus two palette entries is a complete style. Omitted,\nthe style starts from the default light theme as it always did.", "type": [ "string", "null" ] }, "name": { "description": "Human-readable style name; its kebab-case slug seeds the style id.", "type": "string" }, "theme": { "description": "Optional theme document (palette/layer overrides). Merged over\n`base` when one is given; on its own, it replaces the default theme\noutright." } }, "required": [ "name" ], "type": "object" }, "name": "create_style", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "The generated style id — pass it to `set_palette`,\n`set_layer_paint` and `get_style`.", "type": "string" }, "style_url": { "description": "Immutable URL of the compiled style at this version.", "type": "string" }, "theme": { "description": "The theme document that was stored." }, "version": { "description": "The published version (always 1 on create).", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "style_id", "version", "style_url", "theme" ], "type": "object" } }, { "description": "Sample terrain elevation. Provide `points` (a bare list of coordinates) for point elevation, or `encoded_polyline` (optionally with `resample_distance_m`) for an along-route profile — not both. Returns one sample per point/resampled point in order; `elevation_m` is null wherever the engine's DEM tile set has no coverage at that point (never a guess). The `encoded_polyline` form also returns each sample's resampled lat/lon and cumulative `range_km` from the start.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "encoded_polyline": { "description": "A route as a Google encoded polyline with six digits of precision.\nProvide this or `points`, not both.", "type": [ "string", "null" ] }, "points": { "description": "Points to sample. Provide this or `encoded_polyline`, not both.", "items": { "$ref": "#/$defs/LatLon" }, "type": [ "array", "null" ] }, "resample_distance_m": { "description": "Resamples `encoded_polyline` at this spacing in metres before\nsampling height (ignored for `points`).", "format": "double", "type": [ "number", "null" ] } }, "type": "object" }, "name": "elevation", "outputSchema": { "$defs": { "ElevationSample": { "description": "One elevation sample: `elevation_m` and, when the request set\n`encoded_polyline`, the resampled point and its cumulative distance\nfrom the start.", "properties": { "elevation_m": { "description": "Elevation in metres, or `null` when the engine has no DEM tile\ncoverage at this point — Valhalla's own honest value, never a\nguess.", "format": "double", "type": [ "number", "null" ] }, "lat": { "description": "Latitude of the (possibly resampled) point, when known.", "format": "double", "type": [ "number", "null" ] }, "lon": { "description": "Longitude of the (possibly resampled) point, when known.", "format": "double", "type": [ "number", "null" ] }, "range_km": { "description": "Cumulative distance from the first point in kilometres, present\nonly for the `encoded_polyline` form.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "samples": { "description": "One sample per input point (or per resampled shape point, in the\n`encoded_polyline` form), in order.", "items": { "$ref": "#/$defs/ElevationSample" }, "type": "array" } }, "required": [ "samples" ], "type": "object" } }, { "description": "Area in square metres enclosed by a ring of 3+ coordinates, computed geodesically. Always positive: the answer does not depend on whether the ring is wound clockwise or anticlockwise. Intended for zones and boundaries, not for polygons covering more than half the globe. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The coordinates to consider.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" } }, "required": [ "points" ], "type": "object" }, "name": "geo_area", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "area_sq_m": { "description": "Enclosed area in square metres, always positive regardless of\nwinding order.", "format": "double", "type": "number" } }, "required": [ "area_sq_m" ], "type": "object" } }, { "description": "The axis-aligned bounding box enclosing 1+ coordinates, as {min_lat, min_lon, max_lat, max_lon}. Useful for fitting a map view to a set of stops. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The coordinates to consider.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" } }, "required": [ "points" ], "type": "object" }, "name": "geo_bbox", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "max_lat": { "description": "Maximum latitude (north edge).", "format": "double", "type": "number" }, "max_lon": { "description": "Maximum longitude (east edge).", "format": "double", "type": "number" }, "min_lat": { "description": "Minimum latitude (south edge).", "format": "double", "type": "number" }, "min_lon": { "description": "Minimum longitude (west edge).", "format": "double", "type": "number" } }, "required": [ "min_lat", "min_lon", "max_lat", "max_lon" ], "type": "object" } }, { "description": "Initial bearing from one coordinate to another, in degrees clockwise from true north (0-360). This is the bearing at the START of the geodesic; over long distances the bearing changes en route. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "from": { "$ref": "#/$defs/LatLon", "description": "Start coordinate." }, "to": { "$ref": "#/$defs/LatLon", "description": "End coordinate." } }, "required": [ "from", "to" ], "type": "object" }, "name": "geo_bearing", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "bearing_deg": { "description": "Initial bearing from `from` to `to`, degrees clockwise from true\nnorth, normalised to 0–360. Note this is the bearing at the start\nof the geodesic: over long distances the bearing changes en route.", "format": "double", "type": "number" } }, "required": [ "bearing_deg" ], "type": "object" } }, { "description": "The centroid (geometric mean position) of 1+ coordinates, e.g. to pick a depot location or centre a map. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The coordinates to consider.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" } }, "required": [ "points" ], "type": "object" }, "name": "geo_centroid", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "point": { "$ref": "#/$defs/LatLon", "description": "The computed coordinate." } }, "required": [ "point" ], "type": "object" } }, { "description": "The coordinate reached by travelling `distance_m` metres from `from` on `bearing_deg` (degrees clockwise from true north). The inverse of `geo_distance` + `geo_bearing`. Local computation: no network call.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "bearing_deg": { "description": "Bearing in degrees clockwise from true north.", "format": "double", "type": "number" }, "distance_m": { "description": "Distance to travel in metres.", "format": "double", "type": "number" }, "from": { "$ref": "#/$defs/LatLon", "description": "Starting coordinate." } }, "required": [ "from", "bearing_deg", "distance_m" ], "type": "object" }, "name": "geo_destination", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "point": { "$ref": "#/$defs/LatLon", "description": "The computed coordinate." } }, "required": [ "point" ], "type": "object" } }, { "description": "Distance in metres between two coordinates. Computed geodesically on the WGS84 ellipsoid, so it is the straight-line (as-the-crow-flies) distance, NOT a driving distance — use `route` or `matrix` for travel distance and time. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "from": { "$ref": "#/$defs/LatLon", "description": "Start coordinate." }, "to": { "$ref": "#/$defs/LatLon", "description": "End coordinate." } }, "required": [ "from", "to" ], "type": "object" }, "name": "geo_distance", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "distance_m": { "description": "Distance in metres along the WGS84 ellipsoid.", "format": "double", "type": "number" } }, "required": [ "distance_m" ], "type": "object" } }, { "description": "Total length in metres of a polyline through 2+ coordinates, summed geodesically. This measures the line you supply, NOT a driven route — use `route` for that. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The coordinates to consider.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" } }, "required": [ "points" ], "type": "object" }, "name": "geo_length", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "length_m": { "description": "Total geodesic length in metres.", "format": "double", "type": "number" } }, "required": [ "length_m" ], "type": "object" } }, { "description": "The closest position on a polyline to a given coordinate, plus the geodesic distance to it in metres. The answer may lie between vertices, not only on them. Useful for 'how far is this address from the route?'. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "line": { "description": "The polyline's coordinates, 2 or more.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" }, "point": { "$ref": "#/$defs/LatLon", "description": "The coordinate to measure from." } }, "required": [ "point", "line" ], "type": "object" }, "name": "geo_nearest_point_on_line", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "distance_m": { "description": "Geodesic distance from the input point to that position, metres.", "format": "double", "type": "number" }, "point": { "$ref": "#/$defs/LatLon", "description": "The closest position on the line, which may lie between vertices." } }, "required": [ "point", "distance_m" ], "type": "object" } }, { "description": "Whether a coordinate lies inside a polygon: delivery zones, catchments, congestion or clean-air zones, site boundaries. Provide `point` {lat, lon} and `polygon` as 3+ {lat, lon} coordinates of the outer ring (closed automatically if the last does not repeat the first). Points exactly on the boundary count as OUTSIDE. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "point": { "$ref": "#/$defs/LatLon", "description": "The coordinate to test." }, "polygon": { "description": "The polygon's outer ring, 3 or more coordinates. Closed\nautomatically if the last point does not repeat the first.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" } }, "required": [ "point", "polygon" ], "type": "object" }, "name": "geo_point_in_polygon", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "inside": { "description": "True when the point is strictly inside the ring. Points exactly on\nthe boundary are **not** counted as inside.", "type": "boolean" } }, "required": [ "inside" ], "type": "object" } }, { "description": "Reduce the number of coordinates in a polyline while keeping its shape (Douglas-Peucker). `tolerance_deg` is in DEGREES, not metres: about 0.0001 drops detail finer than roughly 10 m at the equator. Endpoints are always kept. Returns the retained points and how many were removed. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The polyline's coordinates, 2 or more.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" }, "tolerance_deg": { "description": "Douglas-Peucker tolerance in **degrees**, not metres. Around\n0.0001 drops detail finer than roughly 10 m at the equator.", "format": "double", "type": "number" } }, "required": [ "points", "tolerance_deg" ], "type": "object" }, "name": "geo_simplify", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "points": { "description": "The retained coordinates, endpoints always preserved.", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" }, "removed": { "description": "How many coordinates were removed.", "format": "uint", "minimum": 0, "type": "integer" } }, "required": [ "points", "removed" ], "type": "object" } }, { "description": "Turn a place (address, POI, town) into coordinates. Ask two ways, and they combine: `query` is free text — one run-together string, the way a person types into a search box — and `street`, `housenumber`, `city`, `postcode` and `country` name the parts of an address separately. At least one of the two is required. Pass the parts whenever you already hold the address in parts (a form, a CRM row, a manifest): components are REQUIREMENTS, not hints, so `city: \"London\"` means a result outside London cannot come back at all, where \"London\" inside `query` only reorders. `country` takes an ISO 3166-1 alpha-2 code or a country name (\"GB\", \"United Kingdom\"); a value naming no country is refused rather than silently matching nothing. Returns up to `limit` (default 10) candidates with name, one-line label, lat/lon, type and address parts. Pass `focus` {lat, lon} to rank results near a location higher. A bare city or town name (no comma) lists that city's airports and main railway stations directly behind it, ahead of namesakes elsewhere. Each hit also carries `match`: a per-component matched/inferred/unmatched verdict, the `score_gap` to the runner-up, and which backend answered. READ IT before acting on an address — an unmatched or inferred postcode on the top hit means the answer does not carry the address you asked for, and a small `score_gap` means the ranking barely chose, so show the alternatives instead of picking one. Use `verify_places` when the address came from a model or a user and needs checking rather than using. Results are matched in `lang` (default \"en\"), so English exonyms — \"Munich\", \"Cologne\", \"Geneva\" — resolve to the place meant; pass `lang` when querying in another language, or \"default\" for each place's local name.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "city": { "description": "Structured component: town or city, e.g. \"London\". Matches the\ncontaining city as well as the immediate locality, so a suburb name\nworks here too.", "type": [ "string", "null" ] }, "country": { "description": "Structured component: ISO 3166-1 alpha-2 code or country name —\n\"GB\", \"gb\", \"United Kingdom\" and \"UK\" all mean the same country.\nA value naming no country is refused rather than quietly applied as\na filter that matches nothing.", "type": [ "string", "null" ] }, "focus": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Optional location bias: results near this point rank higher." }, "housenumber": { "description": "Structured component: house number, e.g. \"10\" or \"221B\". Only\nmeaningful alongside `street` — a house number on its own excludes\nnearly everything and identifies nothing.", "type": [ "string", "null" ] }, "lang": { "description": "Language of the place names to match and return, as a two-letter\ncode. Defaults to \"en\", which is what makes English exonyms\n(\"Munich\", \"Cologne\", \"Geneva\") resolve to the place meant rather\nthan a same-named town elsewhere. Set it to the language your\nquery is written in; \"default\" asks for each place's own local\nname. Deployments support a fixed set (this one: \"en\", \"de\",\n\"fr\"), and anything outside it is refused.", "type": [ "string", "null" ] }, "limit": { "description": "Maximum number of results (1–50, default 10).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "postcode": { "description": "Structured component: postcode in any spacing or case — \"SW1A 2AA\"\nand \"sw1a2aa\" are one query. A bare UK outward code (\"SW1A\")\nselects the whole district.", "type": [ "string", "null" ] }, "query": { "description": "Free-text place query, e.g. \"Dover ferry terminal\". Required unless\nat least one structured component is supplied.", "type": [ "string", "null" ] }, "street": { "description": "Structured component: street name, e.g. \"Downing Street\". Matches\nthe street of addresses and POIs (transliterated street names\nincluded) as well as the street itself.", "type": [ "string", "null" ] } }, "type": "object" }, "name": "geocode", "outputSchema": { "$defs": { "GeocodeHit": { "description": "One geocoding result.", "properties": { "city": { "description": "City or town, when known.", "type": [ "string", "null" ] }, "country": { "description": "Country, when known.", "type": [ "string", "null" ] }, "label": { "description": "Human-readable one-line label assembled from the address parts.", "type": "string" }, "lat": { "description": "Latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees.", "format": "double", "type": "number" }, "match": { "anyOf": [ { "$ref": "#/$defs/GeocodeMatch" }, { "type": "null" } ], "description": "How far this hit can be trusted to be the place that was asked\nfor — see [`GeocodeMatch`]. Present whenever the MapMap gateway\nanswered; absent on a deployment falling back to the direct Photon\ngeocoder, and absent on the gateway's own fast paths (a pasted\ncoordinate pair, a bare UK outward code, a category browse), which\nanswer without a ranking to report on." }, "name": { "description": "Place name, when the source feature has one.", "type": [ "string", "null" ] }, "postcode": { "description": "Postcode, when known.", "type": [ "string", "null" ] }, "type": { "description": "Feature type, e.g. \"house\", \"street\", \"city\" (falls back to the\nOSM value when the endpoint does not classify).", "type": [ "string", "null" ] } }, "required": [ "label", "lat", "lon" ], "type": "object" }, "GeocodeMatch": { "description": "How well one geocoding result answers what was actually asked.\n\nGeocoding's real failure mode is not \"no answer\" but a confident answer\nto a different question: a plausible row on the wrong street, with\nnothing in the response to say so. This object is that missing say-so,\nand an agent should read it before acting on an address.\n\nHow to read it:\n\n* Any component `unmatched` or `inferred` on the TOP hit means the\n answer does not carry the address that was asked for — an `unmatched`\n postcode means the result has no postcode at all, `inferred` means it\n has a different one. Neither is a match. Say so rather than presenting\n the hit as the address, and reach for `verify_places` when the address\n came from a model or a user and needs checking rather than using.\n* A small `score_gap` means the ranking barely chose between this hit\n and the runner-up, which is exactly when to show the alternatives\n instead of picking one for the user.", "properties": { "components": { "$ref": "#/$defs/GeocodeMatchComponents", "description": "Per-component verdict on this hit: one entry for each structured\ncomponent supplied, and empty when the query was free text only." }, "score_gap": { "description": "The top result's score minus the runner-up's, rounded to 3 decimal\nplaces. `0` for a single result, and `0` from the `photon` source,\nwhich publishes no per-result score — so a `0` is \"no signal\", not\n\"a tie\".", "format": "double", "type": "number" }, "source": { "description": "Which backend answered: \"mapmap-index\" (the first-party index) or\n\"photon\".", "type": "string" } }, "required": [ "components", "score_gap", "source" ], "type": "object" }, "GeocodeMatchComponents": { "description": "Per-component verdicts inside a [`GeocodeMatch`]. Each is one of\n\"matched\", \"inferred\" or \"unmatched\"; a component that was not supplied\nis absent entirely.\n\n* \"matched\" — the result's own field carries the value asked for (case-\n and accent-insensitive, and by containment, so `city: \"London\"`\n matches \"City of London\").\n* \"inferred\" — the result carries a value for that component, but not\n the one asked for. It reached the page through ranking, as when a\n street is found by its transliterated name and displayed under its\n canonical one.\n* \"unmatched\" — the result carries no value for that component at all.", "properties": { "city": { "description": "Verdict on the supplied `city`.", "type": [ "string", "null" ] }, "country": { "description": "Verdict on the supplied `country`.", "type": [ "string", "null" ] }, "housenumber": { "description": "Verdict on the supplied `housenumber`.", "type": [ "string", "null" ] }, "postcode": { "description": "Verdict on the supplied `postcode`.", "type": [ "string", "null" ] }, "street": { "description": "Verdict on the supplied `street`.", "type": [ "string", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "results": { "description": "Matching places, best first.", "items": { "$ref": "#/$defs/GeocodeHit" }, "type": "array" } }, "required": [ "results" ], "type": "object" } }, { "description": "Read an asynchronous job submitted with `submit_optimise_job`: its status, and once it has finished, its result inline — exactly the body the synchronous tool would have returned. Status is `queued`, `running`, `succeeded` or `failed`; the answer's `terminal` field says whether the job will ever leave the status it is in, so poll while that is false. POLLING IS FREE: the gateway meters the submission and not the reads, deliberately, because a poll that costs quota is a poll a caller rations, and a rationed poll is how a job that finished in ten seconds gets noticed four minutes later. Check every few seconds rather than guessing at a duration. `units_charged` is what the SUBMISSION drew, and `refunded` says whether a failure handed it back — a failed job shows both, because reporting zero would be a lie about what was charged. Webhooks are the alternative to polling and exist for humans wiring infrastructure, not for agents in a loop. A job belongs to the key that submitted it (or another key of the same identity); anyone else's id answers NOT FOUND rather than forbidden, because confirming an id exists is itself a disclosure. Requires the MapMap gateway.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "id": { "description": "The job id returned by `submit_optimise_job`.", "type": "string" } }, "required": [ "id" ], "type": "object" }, "name": "get_job", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "created_at": { "description": "RFC 3339 UTC submission time.", "type": "string" }, "error": { "description": "Why it failed, once `status` is `failed`.", "type": [ "string", "null" ] }, "finished_at": { "description": "RFC 3339 UTC time it finished, either way.", "type": [ "string", "null" ] }, "id": { "description": "The job id.", "type": "string" }, "kind": { "description": "`optimise`, `replan` or `matrix`.", "type": "string" }, "refunded": { "description": "Whether a failure refunded the submission's units.", "type": "boolean" }, "result": { "description": "The answer, inline, once `status` is `succeeded` — exactly the body\nthe synchronous tool would have returned. A matrix job answers\n`durations_s` and `distances_m`, as the `matrix` tool does; it also\nstill carries the same numbers as `durations` and `distances`, which\nare DEPRECATED and will be removed in a future release." }, "started_at": { "description": "RFC 3339 UTC time a worker picked it up.", "type": [ "string", "null" ] }, "status": { "description": "`queued`, `running`, `succeeded` or `failed`. Only `succeeded` and\n`failed` are terminal; keep polling on the other two.", "type": "string" }, "terminal": { "description": "Whether `status` is one this job will never leave.", "type": "boolean" }, "units_charged": { "description": "Metered units the SUBMISSION drew. This is what was charged;\n`refunded` says whether it came back.", "format": "int64", "type": "integer" }, "webhook_status": { "description": "`delivered` or `delivery_failed`, once a webhook was attempted.", "type": [ "string", "null" ] } }, "required": [ "id", "kind", "status", "terminal", "created_at", "units_charged", "refunded" ], "type": "object" } }, { "description": "Fetch a hosted style's latest theme document (the editable source) and the URL of its latest compiled MapLibre style. Use the theme to inspect current palette and layer overrides before editing.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "Hosted style id, e.g. \"midnight-fleet-a1b2c3\".", "type": "string" } }, "required": [ "style_id" ], "type": "object" }, "name": "get_style", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "Hosted style id.", "type": "string" }, "style_url": { "description": "URL of the latest compiled MapLibre style (point MapLibre GL at\nit).", "type": "string" }, "theme": { "description": "The latest theme document — the editable source the next version\nis published from." } }, "required": [ "style_id", "style_url", "theme" ], "type": "object" } }, { "description": "Check what your own API key has spent, so you can decide mid-task whether to keep going. Reading it is free: it costs no quota. Optional `from` and `to` (YYYY-MM-DD UTC, inclusive, at most 92 days apart) bound the report; omitted, it covers the current month to date. Returns `days` (per-day, per-endpoint), `totals` per endpoint over the range, `total_units`, plus `month_used_units` against `monthly_quota_units` and the prepaid `balance_millipence` (thousandths of a penny). Everything is counted in UNITS — weighted quota units, where a heavier endpoint costs more than one unit per request — so never report these figures as a number of calls. When `identity_pooled` is true the quota is shared with the other keys belonging to the same owner, so these figures are not yours alone. The key that authenticates the call is the key reported on: there is no way to read another caller's usage. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "from": { "description": "First day to report, `YYYY-MM-DD` (UTC) inclusive. Defaults to the\nfirst day of the current month. At most 92 days may separate\n`from` and `to`.", "type": [ "string", "null" ] }, "to": { "description": "Last day to report, `YYYY-MM-DD` (UTC) inclusive. Defaults to\ntoday.", "type": [ "string", "null" ] } }, "type": "object" }, "name": "get_usage", "outputSchema": { "$defs": { "UsageDay": { "description": "One day's usage, in units per endpoint.", "properties": { "day": { "description": "The day, `YYYY-MM-DD`.", "type": "string" }, "endpoints": { "description": "Units per endpoint on this day, heaviest first.", "items": { "$ref": "#/$defs/UsageEndpointTotal" }, "type": "array" }, "total_units": { "description": "Total units on this day.", "format": "int64", "type": "integer" } }, "required": [ "day", "endpoints", "total_units" ], "type": "object" }, "UsageEndpointTotal": { "description": "Units attributed to one endpoint.", "properties": { "endpoint": { "description": "The endpoint label the meter records, e.g. `/route`, `/geocode`,\n`/tiles`. Labels are collapsed by the meter, so several request\nshapes can share one.", "type": "string" }, "units": { "description": "Weighted quota units, never a count of calls.", "format": "int64", "type": "integer" } }, "required": [ "endpoint", "units" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "balance_millipence": { "description": "Prepaid balance in millipence (thousandths of a penny), for usage\nbeyond the monthly allowance.", "format": "int64", "type": "integer" }, "days": { "description": "Per-day breakdown over the range, oldest first. A day with no\nusage is absent rather than reported as zero.", "items": { "$ref": "#/$defs/UsageDay" }, "type": "array" }, "from": { "description": "First day covered, `YYYY-MM-DD` inclusive.", "type": "string" }, "identity_pooled": { "description": "Whether quota is pooled across every key belonging to the same\nidentity. When true, these figures are the identity's shared\nconsumption, so another key of the same owner also spends them.", "type": "boolean" }, "key_id": { "description": "Identifier of the key this usage belongs to.", "type": "string" }, "month_used_units": { "description": "Units counted against the monthly quota right now: the very number\nthe quota check enforces on, independent of `from`/`to`. Quota is\nmonthly, so this — not `total_units` — is what to compare with\n`monthly_quota_units`. It includes units recorded but not yet\nwritten to the daily counters, so over a whole-month range it can\nexceed `total_units`; that is not a discrepancy.", "format": "int64", "type": "integer" }, "monthly_quota_units": { "description": "The key's monthly allowance in units.", "format": "int64", "type": "integer" }, "to": { "description": "Last day covered, `YYYY-MM-DD` inclusive.", "type": "string" }, "total_units": { "description": "Total units over the whole range.", "format": "int64", "type": "integer" }, "totals": { "description": "Units per endpoint over the whole range.", "items": { "$ref": "#/$defs/UsageEndpointTotal" }, "type": "array" } }, "required": [ "key_id", "identity_pooled", "from", "to", "days", "totals", "total_units", "month_used_units", "monthly_quota_units", "balance_millipence" ], "type": "object" } }, { "description": "Three to five short spoken lines an hour about the ground a route is on: one sentence, at the point the driver reaches the thing it is about, and then silence. Not a tour and not a chatbot. NOTHING IS GENERATED HERE: every line is a sentence a person wrote from a public-domain plaque inscription and a person reviewed, compiled into the gateway binary, so no model runs in this path and the lines are identical for every driver. Give `geometry_polyline6` from the `route` tool and, IMPORTANT, `duration_s` from the same answer: without it the silence budget falls back to a distance, and \"four lines an hour\" is a claim about time that a distance answers wrongly at both ends. Optional `min_gap_s`, `min_gap_m`, `max_offset_m` (default 60, hard ceiling 80), `max_lines` and `voice_format` (\"opus\" or \"m4a\", which adds each line's audio cache key and URL and NEVER synthesises). COVERAGE IS ONE CORRIDOR, deliberately: the Chelsea riverside, 18 reviewed lines over 5 km. EVERY OTHER ROUTE ANSWERS WITH NO LINES, and that is a correct answer rather than a failure. ALWAYS READ THE CENSUS, which is the record of WHAT WAS NOT SAID: no lines with every entry in `beyond_reach` means this feature does not cover the journey; no lines with entries in `silenced_by_budget` means the corridor is covered and the budget is holding them back. They are different answers and must never be relayed the same way. On the reference route the census reports 1 spoken and 17 silenced, which is the design working, not a thin corpus. Every line publishes `offset_m`, the measured distance from the plaque to the route, because it is the entire warrant for the word \"here\", and `plaque_ids` so any claim can be taken back to its source. TWO OMISSIONS ARE DELIBERATE: no line says anything is visible, still standing or unchanged (a plaque records that somebody was somewhere, and nothing more), and NO LINE SAYS WHICH SIDE OF THE ROAD anything is on, because nothing in the source records it and a look that finds nothing spends the driver's trust in everything else. Do not add either. COSTS 1 UNIT a call: it reads no tiles and opens no archive. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "duration_s": { "description": "That route's own duration, seconds: `duration_s` from the same\nanswer. **Supply it.** Without it the silence budget falls back to\na distance, and \"three to five lines an hour\" is a claim about\ntime that a distance answers wrongly at both ends.", "format": "double", "type": [ "number", "null" ] }, "geometry_polyline6": { "description": "The route shape, precision-6 encoded: `geometry_polyline6`\nstraight out of the `route` tool's answer.", "type": "string" }, "max_lines": { "description": "Ceiling on how many lines come back, 1 to 50 (default 12).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "max_offset_m": { "description": "How near a plaque must come to the route before its line may be\nspoken, metres (default 60, hard ceiling 80).", "format": "double", "type": [ "number", "null" ] }, "min_gap_m": { "description": "Least distance between two lines, metres (default 400). A floor\nunder the time budget in every case.", "format": "double", "type": [ "number", "null" ] }, "min_gap_s": { "description": "Least time between two lines, seconds (default 900, which is four\nan hour). Applied only with `duration_s`.", "format": "double", "type": [ "number", "null" ] }, "voice_format": { "description": "Container to address each line's audio clip in: `opus` or `m4a`.\nGiven, every line gains a `clip` with its synthesis cache key and\nURL. It NEVER synthesises: the key is a pure function of the\nsentence, so asking here buys nothing.", "type": [ "string", "null" ] } }, "required": [ "geometry_polyline6" ], "type": "object" }, "name": "heritage_narration", "outputSchema": { "$defs": { "HeritageLine": { "description": "One reviewed line about the ground the route is on.", "properties": { "at_m": { "description": "Distance along the route where this belongs, metres.", "format": "double", "type": "number" }, "basis": { "description": "What the line rests on, in words.", "type": "string" }, "clip": { "description": "The line's audio address (only when `voice_format` was given):\n`hash`, `url` and `format`. Nothing was synthesised to produce it." }, "id": { "description": "The corpus entry's own id, stable across responses, so a client\ncan remember which lines it has already played.", "type": "string" }, "offset_m": { "description": "Measured perpendicular distance from the plaque's own recorded\ncoordinate to the route, metres. Published on every line because\nit is the entire warrant for the word \"here\", and because the\ncoordinate behind it was recorded by a volunteer and has never\nbeen surveyed.", "format": "double", "type": "number" }, "plaque_ids": { "description": "The source plaque records, by their OpenPlaques id, so any claim\ncan be taken back to its source.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": "array" }, "say": { "description": "The sentence to say, verbatim. Written by a person from a\npublic-domain plaque inscription and reviewed by a person.", "type": "string" }, "subject": { "description": "The subject's name as the source dataset records it.", "type": "string" } }, "required": [ "at_m", "offset_m", "say", "subject", "basis", "id" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "attribution": { "description": "The licence notice for this answer.", "type": "string" }, "caveat": { "description": "The standing limits, for relaying to users.", "type": "string" }, "census": { "description": "The census of what was NOT said: `corpus_entries`, `within_reach`,\n`beyond_reach`, `spoken`, `silenced_by_budget`, the gap that was\napplied and what it was derived from. An empty `lines` with every\nentry in `beyond_reach` means this feature does not cover the\njourney; an empty `lines` with entries in `silenced_by_budget`\nmeans the budget is working. They are different answers and must\nnot be reported the same way." }, "corridor": { "description": "The corridor that was consulted: its id, name, the road it runs\non and the date its lines were reviewed." }, "lines": { "description": "The lines, in route order. Empty is the ordinary answer nearly\neverywhere: read `census` before saying anything about it.", "items": { "$ref": "#/$defs/HeritageLine" }, "type": "array" }, "route_length_m": { "description": "The route's own length, metres.", "format": "double", "type": "number" } }, "required": [ "route_length_m", "corridor", "lines", "census", "attribution", "caveat" ], "type": "object" } }, { "description": "List the canonical place categories you can pass as `category` to `nearby_places` and `search_along_route` (and browse on with `geocode`). Each entry is a `category` token (the exact value to send, e.g. \"fuel\", \"charging_station\", \"hgv_parking\"), the `aliases` that colloquially name it (\"petrol station\", \"EV charger\", \"lorry park\"), and a one-line `description` of what it covers. Read this before guessing a category: a token that is not on this list matches nothing, and quietly returns an empty result rather than an error. Cuisines, brands and names are NOT categories — search those as free text. Sorted by category and identical on every call. Local lookup, no network, no quota.", "inputSchema": { "properties": {}, "type": "object" }, "name": "list_place_categories", "outputSchema": { "$defs": { "PlaceCategory": { "description": "One canonical place category, the phrases that name it, and what it\ncovers.", "properties": { "aliases": { "description": "Colloquial phrases that resolve to this category, sorted. Passing\none of these as free text works too, but the canonical `category`\nis exact.", "items": { "type": "string" }, "type": "array" }, "category": { "description": "The canonical token to pass as a category filter, e.g. `fuel`.", "type": "string" }, "description": { "description": "What the category covers, in one line.", "type": "string" } }, "required": [ "category", "aliases", "description" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "categories": { "description": "Every category `nearby_places`, `search_along_route` and `geocode`\nrecognise, sorted alphabetically by `category`.", "items": { "$ref": "#/$defs/PlaceCategory" }, "type": "array" } }, "required": [ "categories" ], "type": "object" } }, { "description": "List everything a MapMap style theme can style: the first-party named bases a style can start from (pass one as `create_style`'s `base`, or as the static-map API's `style=`), the named palette slots with their light/dark default colours, the skeleton layer ids (paint order) that `set_layer_paint` accepts, and the OpenMapTiles source-layers extra layers may reference. Attribution is enforced on every compiled style and cannot be themed away. Local lookup, no network; always works.", "inputSchema": { "properties": {}, "type": "object" }, "name": "list_style_layers", "outputSchema": { "$defs": { "PaletteSlotInfo": { "description": "One themable palette slot with its built-in light/dark defaults.", "properties": { "dark": { "description": "Default colour on the dark base theme.", "type": "string" }, "light": { "description": "Default colour on the light base theme.", "type": "string" }, "slot": { "description": "Slot name, e.g. \"water\" or \"roadMajor\".", "type": "string" } }, "required": [ "slot", "light", "dark" ], "type": "object" }, "StyleBaseInfo": { "description": "One entry in the named-base catalogue (`sn_style::list_bases`).", "properties": { "description": { "description": "One line saying what the base is for. This is what to choose on.", "type": "string" }, "group": { "description": "\"base\" for the two stock slates (`light`, `dark`), \"designed\" for a\ndrawn style.", "type": "string" }, "id": { "description": "Base id: what `create_style`'s `base` and the static-map API's\n`style=` accept, e.g. \"navigator-night\".", "type": "string" }, "label": { "description": "Short display label, e.g. \"Navigator Night\".", "type": "string" }, "restrictions": { "description": "True when the base is drawn to sit under MapMap's\nvehicle-restriction overlay (only `fleet`). That overlay is a\nmapmap.ai runtime layer, not tile data and not part of any compiled\nstyle, so it draws in Studio and on /map and nowhere else. Asking\nfor this base anywhere else gets the palette alone; for a real\nrestriction answer use `check_clearance_on_route` or\n`check_adr_tunnel`, which answer for an actual vehicle.", "type": "boolean" } }, "required": [ "id", "label", "description", "group", "restrictions" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "attribution": { "description": "The attribution contract: enforced on every compiled style, not\nthemable.", "type": "string" }, "bases": { "description": "The first-party named bases a style can start from, in display\norder: pass one to `create_style`'s `base`, or to the static-map\nAPI's `style=`.", "items": { "$ref": "#/$defs/StyleBaseInfo" }, "type": "array" }, "layer_ids": { "description": "The skeleton layer ids a theme's `layers` may override, in paint\norder (first = bottom).", "items": { "type": "string" }, "type": "array" }, "palette_slots": { "description": "The named palette slots a theme's `palette` may override, with\ntheir light/dark defaults, in presentation order.", "items": { "$ref": "#/$defs/PaletteSlotInfo" }, "type": "array" }, "source_layers": { "description": "The OpenMapTiles source-layers the tiles emit; `extra_layers` must\nreference one of these.", "items": { "type": "string" }, "type": "array" } }, "required": [ "bases", "palette_slots", "layer_ids", "source_layers", "attribution" ], "type": "object" } }, { "description": "Snap a recorded GPS trace to the road network and say what it actually travelled over. Provide `shape` (2 to 2000 recorded points, oldest first) or `encoded_polyline` (the same trace as a polyline6 string) — not both — plus the `costing` it was travelled under: \"auto\" (default), \"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\". Costing decides which roads the trace may match onto, so a walk matched as \"auto\" snaps to the carriageway rather than the footpath. Returns the matched path as `geometry_polyline6` (the snapped roads, not your raw points) with its `distance_m` and `duration_s`, then the roll-ups: `by_road_class` and `by_admin` (distance and time, longest first), `by_surface` (distance), and `toll`, `bridge` and `tunnel` totals. This is how you turn a dashcam or telematics log into a report — which country and region the driving happened in, how much of it was motorway, how much was tolled, how much was unpaved. Honesty: the roll-ups are summed per matched road segment, so they need not add up to `distance_m` exactly, and segments the map records no surface or admin area for are left out of that breakdown rather than filed under a guess — an entry in `by_admin` with null codes is exactly that, counted and not attributed. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY); there is no direct-backend fallback.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "anyOf": [ { "$ref": "#/$defs/CostingKind" }, { "type": "null" } ], "description": "Costing model the trace was travelled under: `auto` (default),\n`truck`, `bicycle`, `pedestrian` or `motor_scooter`. It decides\nwhich roads the trace may be matched onto, so a walked trace\nmatched as `auto` snaps to the carriageway rather than the path." }, "encoded_polyline": { "description": "The trace as a Google encoded polyline with six digits of decimal\nprecision (polyline6) — the geometry `route` and `match_trace`\nthemselves return. Provide this or `shape`.", "type": [ "string", "null" ] }, "shape": { "description": "The trace as an ordered list of recorded points, oldest first.\nBetween 2 and 2000 points. Provide this or `encoded_polyline`.", "items": { "$ref": "#/$defs/LatLon" }, "type": [ "array", "null" ] } }, "type": "object" }, "name": "match_trace", "outputSchema": { "$defs": { "AdminAreaSummary": { "description": "Distance and time the matched path spent in one administrative area.", "properties": { "country": { "default": null, "description": "Country name, or null as for `country_code`.", "type": [ "string", "null" ] }, "country_code": { "default": null, "description": "ISO 3166-1 alpha-2 country code, or null where the routing graph\nplaces the segment in no admin area at all.", "type": [ "string", "null" ] }, "distance_m": { "description": "Distance in this area in metres.", "format": "double", "type": "number" }, "duration_s": { "description": "Time in this area in seconds.", "format": "double", "type": "number" }, "edge_count": { "description": "How many matched segments fell in this area.", "format": "uint64", "minimum": 0, "type": "integer" }, "state": { "default": null, "description": "State/region name, or null when the graph records none.", "type": [ "string", "null" ] }, "state_code": { "default": null, "description": "State/region code, or null when the graph records none.", "type": [ "string", "null" ] } }, "required": [ "distance_m", "duration_s", "edge_count" ], "type": "object" }, "RoadClassSummary": { "description": "Distance and time the matched path spent on one road class.", "properties": { "distance_m": { "description": "Distance on this road class in metres.", "format": "double", "type": "number" }, "duration_s": { "description": "Time on this road class in seconds.", "format": "double", "type": "number" }, "edge_count": { "description": "How many matched segments carried this road class.", "format": "uint64", "minimum": 0, "type": "integer" }, "road_class": { "description": "The road class, as the routing graph records it: `motorway`,\n`trunk`, `primary`, `secondary`, `tertiary`, `unclassified`,\n`residential`, `service_other`.", "type": "string" } }, "required": [ "road_class", "distance_m", "duration_s", "edge_count" ], "type": "object" }, "SegmentSummary": { "description": "How much of the matched path carried one flag (toll, bridge, tunnel).\nThe engine reports no separate time for these, so distance and a count\nare all that can honestly be given.", "properties": { "distance_m": { "description": "Distance carrying this flag in metres. Zero means none of the\nmatched path did.", "format": "double", "type": "number" }, "edge_count": { "description": "How many matched segments carried this flag.", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "distance_m", "edge_count" ], "type": "object" }, "SurfaceSummary": { "description": "Distance the matched path spent on one road surface.", "properties": { "distance_m": { "description": "Distance on this surface in metres.", "format": "double", "type": "number" }, "edge_count": { "description": "How many matched segments carried this surface.", "format": "uint64", "minimum": 0, "type": "integer" }, "surface": { "description": "The surface, as the routing graph records it: `paved_smooth`,\n`paved`, `paved_rough`, `compacted`, `dirt`, `gravel`, `path`,\n`impassable`.", "type": "string" } }, "required": [ "surface", "distance_m", "edge_count" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "bridge": { "$ref": "#/$defs/SegmentSummary", "description": "Bridge portion of the matched path." }, "by_admin": { "description": "Distance and time by administrative area, longest first. An entry\nwhose codes are all null covers segments the graph could not place\nin any admin area — counted honestly rather than guessed at.", "items": { "$ref": "#/$defs/AdminAreaSummary" }, "type": "array" }, "by_road_class": { "description": "Distance and time by road class (`motorway`, `primary`,\n`residential`, …), longest first.", "items": { "$ref": "#/$defs/RoadClassSummary" }, "type": "array" }, "by_surface": { "description": "Distance by road surface (`paved`, `paved_smooth`, `gravel`, …),\nlongest first. Empty when the graph records no surface for any\nmatched segment.", "items": { "$ref": "#/$defs/SurfaceSummary" }, "type": "array" }, "costing": { "description": "Costing the trace was matched under.", "type": "string" }, "distance_m": { "description": "Length of the matched path in metres.", "format": "double", "type": "number" }, "duration_s": { "description": "Travel time along the matched path in seconds, from the engine's\nown time model.", "format": "double", "type": "number" }, "edge_count": { "description": "How many road segments the trace matched onto.", "format": "uint64", "minimum": 0, "type": "integer" }, "geometry_polyline6": { "description": "The matched path as a Google encoded polyline with six digits of\ndecimal precision (polyline6). This is the trace snapped to real\nroads, not the raw input.", "type": "string" }, "summary": { "description": "One-line human summary of the match, for reading aloud.", "type": "string" }, "toll": { "$ref": "#/$defs/SegmentSummary", "description": "Tolled portion of the matched path." }, "tunnel": { "$ref": "#/$defs/SegmentSummary", "description": "Tunnel portion of the matched path." } }, "required": [ "geometry_polyline6", "distance_m", "duration_s", "summary", "costing", "edge_count", "by_road_class", "by_admin", "by_surface", "toll", "bridge", "tunnel" ], "type": "object" } }, { "description": "Compute a travel time/distance matrix between origins (rows) and destinations (columns). Costing \"auto\", \"truck\" (with optional `truck` profile as in `route`), \"bicycle\", \"pedestrian\" or \"motor_scooter\". Returns durations_s[i][j] in seconds and distances_m[i][j] in metres; null cells are unreachable pairs. Up to 10,000 cells per call (origins × destinations). Optional `exclude_polygons` for before/after scenarios (\"close this bridge and recompute the matrix\"): an array of polygons, each an array of [lon, lat] pairs forming one ring — longitude FIRST — whose intersecting roads are excluded from every cell's path finding. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model: \"auto\" (default), \"truck\", \"bicycle\", \"pedestrian\"\nor \"motor_scooter\"." }, "destinations": { "description": "Destination locations (matrix columns).", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" }, "exclude_polygons": { "description": "Areas to avoid — scenario analysis (\"close this bridge and\nrecompute the matrix\"): an array of polygons, each an array of\n`[lon, lat]` pairs forming one exterior ring (GeoJSON-style,\nlongitude FIRST). Roads intersecting any ring are excluded from\nevery cell's path finding. Applies to the Valhalla engine;\nunsupported on the GraphHopper engine, where it is ignored.", "items": { "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" }, "type": [ "array", "null" ] }, "origins": { "description": "Origin locations (matrix rows).", "items": { "$ref": "#/$defs/LatLon" }, "type": "array" }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"." } }, "required": [ "origins", "destinations" ], "type": "object" }, "name": "matrix", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "distances_m": { "description": "Travel distances in metres, same shape as `durations_s`. Null\ncells are unreachable pairs.", "items": { "items": { "format": "double", "type": [ "number", "null" ] }, "type": "array" }, "type": "array" }, "durations_s": { "description": "Travel times in seconds; `durations_s[i][j]` is origin `i` →\ndestination `j`. Null cells are unreachable pairs.", "items": { "items": { "format": "double", "type": [ "number", "null" ] }, "type": "array" }, "type": "array" } }, "required": [ "durations_s", "distances_m" ], "type": "object" } }, { "description": "Find places near a point, nearest first with distance in metres — by category (cafes, fuel, EV charging, parking), by name or brand (\"the nearest Lloyds bank\", \"nearest Sainsbury's\"), or both. Use this instead of geocode whenever the question is about what is NEAR a location: geocode ranks a brand's branches everywhere and only biases by proximity, so it will happily return a Lloyds in another city over the one 100 m away. Provide `lat`, `lon` and at least one of `category` or `name`. `category` is matched against the map's lowercased OSM tag values (amenity/shop/tourism/…), e.g. \"cafe\", \"fuel\", \"charging_station\", \"parking\", \"pharmacy\", \"supermarket\", \"hotel\", \"restaurant\", \"fast_food\", \"atm\", \"bakery\", \"hospital\", \"station\"; common colloquial names are normalised (\"coffee\" -> cafe, \"ev_charging\" -> charging_station, \"petrol\" -> fuel, \"chemist\" -> pharmacy). `name` matches the place's name or alternative names word by word, case- and accent-insensitively, with the last word also matching as a prefix. Combine the two to disambiguate a brand — \"Lloyds\" plus \"bank\" excludes Lloyds Pharmacy. A category or name the map does not carry returns an empty list, never an error. Optional `radius_m` (default 2500, max 100000) bounds the straight-line search distance and `limit` (default 5, max 10) the result count. Each result has name, one-line label, lat/lon, address parts, distance_m, categories and a `details` object of display tags (opening_hours, website, phone, ...) when the map carries them. Every result also carries `bearing_deg` and a spoken `direction`. Pass `heading_deg` (degrees clockwise from true north, 0 = north, 90 = east) and results are described from where the user stands — \"ahead and slightly to your right, about 80 metres\" — with a signed `relative_bearing_deg` (negative left, positive right); without a heading the phrasing falls back to cardinals (\"to the north-east\"), so this works with or without a compass. Add `fov_deg` to keep only what lies within that cone of the heading — it is the FULL width of the cone, so 90 keeps what lies within 45 degrees either side of dead ahead; anything dropped is counted in `out_of_view`, so a non-zero count means there ARE matching places nearby, just not in front of the user — say that rather than \"nothing nearby\". Prefer reading `direction` aloud over coordinates. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "category": { "description": "The kind of place to find. The gateway matches it against the\nindex's lowercased OSM tag values (the value of the POI's\n`amenity`/`shop`/`tourism`/`railway`/… tag) — e.g. \"cafe\", \"fuel\",\n\"charging_station\", \"parking\", \"pharmacy\", \"supermarket\", \"hotel\",\n\"restaurant\", \"fast_food\", \"atm\", \"bakery\", \"hospital\", \"station\" —\nand normalises common colloquial names first (\"coffee\" → cafe;\n\"ev_charging\", \"ev charging\" → charging_station; \"petrol\" → fuel;\n\"chemist\" → pharmacy). A category the index does not carry matches\nnothing: the result is an empty list, not an error. Optional when\n`name` is given; at least one of the two is required.", "type": [ "string", "null" ] }, "fov_deg": { "description": "Field of view: the full width in degrees of a cone centred on\n`heading_deg`, outside which results are dropped — it is the\nFULL width, so 90 keeps only what lies within 45 degrees either\nside of dead ahead. Needs\n`heading_deg` — a cone has to point somewhere. The count of\nresults removed is reported as `out_of_view`.", "format": "double", "type": [ "number", "null" ] }, "heading_deg": { "description": "Which way the user is facing, in degrees **clockwise from true\nnorth** (0 = north, 90 = east, 180 = south, 270 = west). Supply it\nand every result is also described from the user's point of view\n(\"just ahead on your right\"); omit it and results fall back to\ncardinal directions (\"to the north-east\"), so the tool works with\nor without a compass.", "format": "double", "type": [ "number", "null" ] }, "lat": { "description": "Latitude of the search point in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "limit": { "description": "Maximum number of results (1–10, default 5).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "lon": { "description": "Longitude of the search point in decimal degrees (−180 to 180).", "format": "double", "type": "number" }, "name": { "description": "The name or brand of the place to find — \"Lloyds\", \"Lloyds Bank\",\n\"Sainsbury's\" — for \"where is the nearest X\" questions. Every word\nmust appear in the place's name or one of its alternative names,\ncase- and accent-insensitively, with the last word also matching as\na prefix (\"Sains\" finds Sainsbury's). Combine with `category` to\ndisambiguate a brand used by more than one kind of place (\"Lloyds\"\nplus \"bank\" excludes Lloyds Pharmacy). Optional when `category` is\ngiven; at least one of the two is required.", "type": [ "string", "null" ] }, "radius_m": { "description": "Maximum straight-line distance of any result from the point, in\nmetres (1–100000, default 2500).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "lat", "lon" ], "type": "object" }, "name": "nearby_places", "outputSchema": { "$defs": { "NearbyPlace": { "description": "One `nearby_places` result: a place matching the requested category\nand/or name near the queried point. The same shape family as [`GeocodeHit`], plus the\nbrowse-only extras (`distance_m`, `categories`, `details`) and the\negocentric extras (`bearing_deg`, `relative_bearing_deg`, `direction`).", "properties": { "bearing_deg": { "description": "Bearing from the queried point to this place, degrees clockwise\nfrom true north. Always present.", "format": "double", "type": [ "number", "null" ] }, "categories": { "description": "POI categories (e.g. [\"cafe\"]), when the index carries them.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "city": { "description": "City or town, when known.", "type": [ "string", "null" ] }, "country": { "description": "Country, when known: a country name, or the ISO 3166-1 alpha-2\ncode (e.g. \"GB\") on first-party hits, which carry only the code.", "type": [ "string", "null" ] }, "details": { "description": "Whitelisted OSM display tags on POI hits (opening_hours, website,\nphone, brand, cuisine, wheelchair, wikipedia, ...), passed through\nverbatim when present." }, "direction": { "description": "The direction phrased for speech — \"ahead and slightly to your\nright, about 80 metres\" with a heading, \"to the north-east, about\n80 metres\" without one. Deliver this verbatim rather than reading\nout coordinates.", "type": [ "string", "null" ] }, "distance_m": { "description": "Straight-line distance from the queried point in metres.", "format": "double", "type": [ "number", "null" ] }, "label": { "description": "Human-readable one-line label assembled from the address parts.", "type": "string" }, "lat": { "description": "Latitude of the place in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude of the place in decimal degrees.", "format": "double", "type": "number" }, "name": { "description": "Place name, when the source feature has one.", "type": [ "string", "null" ] }, "postcode": { "description": "Postcode, when known.", "type": [ "string", "null" ] }, "relative_bearing_deg": { "description": "Where this place is relative to the way the user is facing:\nnegative to the left, positive to the right, −180 to 180. Present\nonly when the request supplied `heading_deg`.", "format": "double", "type": [ "number", "null" ] }, "type": { "description": "Feature type — \"poi\" for category-browse hits.", "type": [ "string", "null" ] } }, "required": [ "label", "lat", "lon" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "out_of_view": { "description": "How many otherwise-matching places were dropped for falling\noutside `fov_deg`. Non-zero means there are matching places near\nthe user that are simply not in front of them — say so rather\nthan reporting nothing nearby. Always 0 when no field of view was\nset.", "format": "uint32", "minimum": 0, "type": "integer" }, "results": { "description": "Matching places, nearest first. Empty when the index has no such\nplace within the radius.", "items": { "$ref": "#/$defs/NearbyPlace" }, "type": "array" } }, "required": [ "results" ], "type": "object" } }, { "description": "Optimise multi-vehicle, multi-stop delivery plans (VRP). Provide `vehicles` (id, start/end, capacity, skills, time_window), `jobs` (id, location, service_s, delivery/pickup, skills, time_windows) and/or `shipments` (pickup+delivery pairs that ride the same vehicle). Costing \"auto\", \"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\" (cargo-bike and courier fleets welcome): with a `truck` profile (dimensions + ADR declaration, as in `route`), the travel-time matrix respects dimensional and dangerous-goods restrictions, so every optimised route is truck-legal. Returns a summary, unassigned tasks and per-vehicle routes with ordered steps (arrival_s/duration_s in seconds, distance_m in metres). Fair use: at most 200 unique locations per problem, and no wider than the routing engine's matrix span (1,500 km on the hosted endpoint for motor costings, 400 km stock on a self-host). Past that, cluster the stops with `cluster` and optimise each group, or submit the whole problem to the asynchronous lane with `submit_optimise_job` (2,000 locations). Optional `territories` are named polygons ([{id, polygon}], GeoJSON [lon, lat] rings, LONGITUDE FIRST) that bound who serves what: a vehicle listing `territory_ids` may serve a task only if that task sits inside at least one of the territories it names, while a vehicle listing none is unrestricted and may serve anything, inside a round or outside every one. The response's `territories` block says which vehicle was eligible for what and names any task no vehicle could take. A vehicle may also declare `reloads` {max_trips 2-5, reload_time_s, depot?} to return to a depot, reload and go out again — the tipping round. It needs a `time_window`, because the shift is what gets split: it is cut into that many consecutive non-overlapping windows separated by the reload time, each trip carrying the vehicle's FULL capacity and task caps. That split is fixed BEFORE the solve, so the plan is conservative and never optimistic — it cannot put a lorry in two places at once — but it is an approximation: a trip that finishes early cannot lend its spare time to the next, so stops can come back unassigned that a truly sequential model would have served, and `max_trips` is a budget rather than a prediction (ask for five on a shift that supports three and every window shrinks to a fifth). Read the returned `reloads` block before quoting arrival times, and re-plan after each tip with `replan_routes` for the tighter answer. `relax_if_unassigned` {time_windows_by_s?, allow_overtime_s?} re-solves ONCE with those relaxations if the first plan left work unassigned, and the `relaxation` block says honestly which plan came back: at most one second solve, never beyond the caps stated, and the relaxed plan is returned ONLY if it assigns more work than the first. Breaks are never widened — a driver's rest is not a preference to trade for a fuller van — and neither are capacities, skills, territories or task caps; only time windows move. It bills as two solves when the second one runs. Always check `relaxation.relaxed_plan_used` before telling anyone the day fits: a plan produced under relaxation has had promises moved. `emissions` {vehicle_category, fuel, euro_standard} annotates the plan with the clean-air zones its own stops sit in and what this vehicle pays in each; add `avoid_zones: true` to steer the travel-time matrix out of them, which changes the plan itself. Territories, reloads, relaxation and zones are computed by the MapMap gateway; without one configured the tool refuses rather than returning a plan that quietly ignored them.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "EmissionsFuel": { "description": "What a vehicle burns, in clean-air-zone scheme terms.", "oneOf": [ { "const": "petrol", "description": "Petrol, including petrol hybrids (schemes rate a hybrid by its\ncombustion engine's approval).", "type": "string" }, { "const": "diesel", "description": "Diesel, including diesel hybrids.", "type": "string" }, { "const": "electric", "description": "Battery-electric.", "type": "string" }, { "const": "hydrogen", "description": "Hydrogen fuel cell.", "type": "string" }, { "const": "gas", "description": "LPG or CNG; rated as petrol by every scheme in the dataset.", "type": "string" } ] }, "EmissionsSpec": { "description": "A vehicle's emission declaration, for clean-air / low-emission zone\nassessment.", "properties": { "euro_standard": { "description": "Its Euro emission standard, 1–6. Heavy-duty approvals are written\nin Roman numerals (Euro VI); declare Euro VI as `6`. Required for\nany combustion fuel — without it no zone can be resolved, and a\nhalf-declared vehicle is indistinguishable from an undeclared one.\nOptional only for `electric` or `hydrogen`.", "format": "uint8", "maximum": 255, "minimum": 0, "type": [ "integer", "null" ] }, "fuel": { "$ref": "#/$defs/EmissionsFuel", "description": "What it burns." }, "vehicle_category": { "$ref": "#/$defs/EmissionsVehicleCategory", "description": "What kind of vehicle this is, in scheme terms." } }, "required": [ "vehicle_category", "fuel" ], "type": "object" }, "EmissionsVehicleCategory": { "description": "What a vehicle is, in clean-air-zone scheme terms.\n\nDeclaring this turns \"charge depends on vehicle emissions\" into an\nanswer. Without it a zone can only be named, never priced.", "oneOf": [ { "const": "car", "description": "A private car.", "type": "string" }, { "const": "van", "description": "A van or light goods vehicle up to 3.5 tonnes.", "type": "string" }, { "const": "minibus", "description": "A minibus (typically 8+ passenger seats, up to 5 tonnes).", "type": "string" }, { "const": "hgv", "description": "A heavy goods vehicle over 3.5 tonnes.", "type": "string" }, { "const": "bus", "description": "A bus over 5 tonnes.", "type": "string" }, { "const": "coach", "description": "A coach over 5 tonnes.", "type": "string" }, { "const": "taxi", "description": "A licensed hackney carriage.", "type": "string" }, { "const": "phv", "description": "A private hire vehicle.", "type": "string" }, { "const": "motorcycle", "description": "A motorcycle, moped or tricycle.", "type": "string" }, { "const": "motorhome", "description": "A motor caravan or campervan.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OptimiseJobSpec": { "description": "One single-stop job of an optimisation problem.", "properties": { "delivery": { "description": "Quantities delivered to the job (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "id": { "description": "Caller-chosen job id, echoed back in steps and `unassigned`.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "$ref": "#/$defs/LatLon", "description": "Job location." }, "pickup": { "description": "Quantities picked up at the job (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "service_s": { "description": "On-site service time in seconds.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "skills": { "description": "Skills the job requires.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] }, "time_windows": { "description": "Acceptable `[start, end]` windows in seconds.", "items": { "items": { "format": "int64", "type": "integer" }, "type": "array" }, "type": [ "array", "null" ] } }, "required": [ "id", "location" ], "type": "object" }, "OptimiseShipmentSpec": { "description": "A pickup+delivery pair that must ride the same vehicle, pickup first.", "properties": { "amount": { "description": "Quantities moved (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "delivery": { "$ref": "#/$defs/OptimiseShipmentStopSpec", "description": "The delivery end." }, "pickup": { "$ref": "#/$defs/OptimiseShipmentStopSpec", "description": "The pickup end." }, "skills": { "description": "Skills the shipment requires.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] } }, "required": [ "pickup", "delivery" ], "type": "object" }, "OptimiseShipmentStopSpec": { "description": "One end (pickup or delivery) of a shipment.", "properties": { "id": { "description": "Caller-chosen stop id, echoed back in steps and `unassigned`.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "$ref": "#/$defs/LatLon", "description": "Stop location." }, "service_s": { "description": "On-site service time in seconds.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "id", "location" ], "type": "object" }, "OptimiseVehicleSpec": { "description": "One vehicle of an optimisation fleet.", "properties": { "capacity": { "description": "Multidimensional capacity (same length as job `delivery`/`pickup`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "end": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "End location; omitted, the route ends at its last stop." }, "id": { "description": "Caller-chosen vehicle id, echoed back on its route.", "format": "uint64", "minimum": 0, "type": "integer" }, "reloads": { "anyOf": [ { "$ref": "#/$defs/ReloadsSpec" }, { "type": "null" } ], "description": "Let this vehicle return to a depot, reload and go out again — the\nwaste-collection tipping round, the van that comes back for a\nsecond wave of parcels." }, "skills": { "description": "Skills this vehicle provides.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] }, "start": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Start location; at least one of `start`/`end` is required." }, "territory_ids": { "description": "Ids of the request's `territories` this vehicle may work in.\nOmitted or empty, the vehicle is UNRESTRICTED and may serve any\ntask, inside a territory or outside every one of them. Listed, the\nvehicle may serve a task only if that task sits inside at least one\nof the named territories.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "time_window": { "description": "Working window as `[start, end]` in seconds (any consistent epoch).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] } }, "required": [ "id" ], "type": "object" }, "RelaxSpec": { "description": "What the caller is willing to give up if the first solve leaves work\nunassigned. At least one field is required.", "properties": { "allow_overtime_s": { "description": "Extend every vehicle's shift END by this many seconds. Shift starts\nare never moved earlier — a driver cannot begin before they begin.", "format": "int64", "type": [ "integer", "null" ] }, "time_windows_by_s": { "description": "Widen every task time window by this many seconds at EACH end. A\n09:00–12:00 window with 1800 becomes 08:30–12:30.", "format": "int64", "type": [ "integer", "null" ] } }, "type": "object" }, "ReloadsSpec": { "description": "A vehicle's multi-trip reload plan.", "properties": { "depot": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Where the vehicle reloads. Omitted, its own `start` is used (or its\n`end` if it declared only that)." }, "max_trips": { "description": "How many trips this vehicle may run in its shift, 2–5. A BUDGET,\nnot a prediction: the shift is cut into that many fixed windows\nbefore the solve, so asking for five trips on a shift that supports\nthree shrinks every window to a fifth and can make the whole day\nworse. Ask for the number of trips you actually expect to run.", "format": "uint32", "minimum": 0, "type": "integer" }, "reload_time_s": { "description": "Seconds at the depot between trips — tipping, reloading, the\nweighbridge. Held out of the shift before it is partitioned, so it\nis never accidentally spent driving.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "max_trips" ], "type": "object" }, "TerritorySpec": { "description": "One named territory: a polygon that bounds which vehicle may serve\nwhich stop.", "properties": { "id": { "description": "Caller-chosen id, echoed back and referenced by\n`vehicles[].territory_ids`. Must be unique within the request.", "type": "string" }, "polygon": { "description": "The outer ring as GeoJSON `[lon, lat]` positions — longitude\nFIRST. Closed or open; an unclosed ring is closed for you.", "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" } }, "required": [ "id", "polygon" ], "type": "object" }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "avoid_zones": { "description": "Keep the optimisation's travel-time matrix out of every zone the\ndeclared vehicle would be charged or banned in. Requires\n`emissions`.", "type": [ "boolean", "null" ] }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model for the travel-time matrix: \"auto\" (default),\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\"." }, "emissions": { "anyOf": [ { "$ref": "#/$defs/EmissionsSpec" }, { "type": "null" } ], "description": "The fleet's emission declaration, for UK clean-air / low-emission\nzone assessment. On its own it annotates: the response's `zones`\nblock names every zone containing one of the problem's own\nlocations and what this vehicle would pay there. With\n`avoid_zones` it also steers the internal travel-time matrix away\nfrom those zones, so the plan itself changes." }, "jobs": { "description": "Single-stop jobs (at least one job or shipment overall).", "items": { "$ref": "#/$defs/OptimiseJobSpec" }, "type": "array" }, "relax_if_unassigned": { "anyOf": [ { "$ref": "#/$defs/RelaxSpec" }, { "type": "null" } ], "description": "Re-solve ONCE with these relaxations if the first solve leaves work\nunassigned, and say honestly which plan came back. At most one\nsecond solve, never beyond the caps you state, and the relaxed plan\nis returned only if it assigns MORE work than the first — giving\naway constraints for nothing is strictly worse than not giving them\naway. Breaks are never widened, nor are capacities, skills,\nterritories or task caps: only time windows move, and only by the\nstated amounts. Bills as two solves when the second one runs." }, "shipments": { "description": "Pickup+delivery pairs.", "items": { "$ref": "#/$defs/OptimiseShipmentSpec" }, "type": "array" }, "territories": { "description": "Fleet territories: named polygons that bound which vehicle may\nserve which stop, referenced by `vehicles[].territory_ids`. These\nare request data — caller-drawn rounds, validated per call and\nnever stored. Nothing to do with clean-air zones or with the\noffline map packages of the same word.", "items": { "$ref": "#/$defs/TerritorySpec" }, "type": [ "array", "null" ] }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; the travel-time matrix then respects dimensional and\ndangerous-goods restrictions, so the whole plan is truck-legal." }, "vehicles": { "description": "The fleet (at least one vehicle, each with a start and/or end).", "items": { "$ref": "#/$defs/OptimiseVehicleSpec" }, "type": "array" } }, "required": [ "vehicles" ], "type": "object" }, "name": "optimise_routes", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OptimiseSummary": { "description": "Solution summary of the `optimise_routes` tool.", "properties": { "cost": { "description": "Solver cost of the plan (travel seconds under the default model).", "format": "int64", "type": "integer" }, "distance_m": { "description": "Total travel distance in metres, when reported.", "format": "int64", "type": [ "integer", "null" ] }, "duration_s": { "description": "Total travel time in seconds.", "format": "int64", "type": "integer" }, "routes": { "description": "Number of vehicle routes in the plan.", "format": "int64", "type": "integer" }, "service_s": { "description": "Total service time in seconds.", "format": "int64", "type": "integer" }, "unassigned": { "description": "Number of unassigned tasks.", "format": "int64", "type": "integer" }, "waiting_time_s": { "description": "Total waiting time in seconds.", "format": "int64", "type": "integer" } }, "required": [ "cost", "routes", "unassigned", "duration_s", "service_s", "waiting_time_s" ], "type": "object" }, "OptimisedRoute": { "description": "One vehicle's optimised route.", "properties": { "distance_m": { "description": "Total travel distance in metres, when reported.", "format": "int64", "type": [ "integer", "null" ] }, "duration_s": { "description": "Total travel time in seconds.", "format": "int64", "type": "integer" }, "service_s": { "description": "Total on-site service time in seconds.", "format": "int64", "type": "integer" }, "steps": { "description": "Ordered steps: start, tasks in visit order, end.", "items": { "$ref": "#/$defs/OptimisedStep" }, "type": "array" }, "vehicle": { "description": "The vehicle id from the request.", "format": "uint64", "minimum": 0, "type": "integer" }, "waiting_time_s": { "description": "Total waiting time in seconds.", "format": "int64", "type": "integer" } }, "required": [ "vehicle", "duration_s", "service_s", "waiting_time_s", "steps" ], "type": "object" }, "OptimisedStep": { "description": "One step of an optimised vehicle route.", "properties": { "arrival_s": { "description": "Arrival time in seconds (same epoch as the request's windows).", "format": "int64", "type": "integer" }, "duration_s": { "description": "Cumulative travel time when the step begins, in seconds.", "format": "int64", "type": "integer" }, "id": { "description": "The job/shipment-stop id, absent on start/end steps.", "format": "uint64", "minimum": 0, "type": [ "integer", "null" ] }, "load": { "description": "Vehicle load after the step, when reported.", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "location": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "The step's location resolved back to coordinates." }, "service_s": { "description": "On-site service time in seconds.", "format": "int64", "type": "integer" }, "type": { "description": "Step kind: \"start\", \"job\", \"pickup\", \"delivery\", \"break\" or \"end\".", "type": "string" }, "waiting_time_s": { "description": "Waiting time before the step in seconds.", "format": "int64", "type": "integer" } }, "required": [ "type", "arrival_s", "duration_s", "service_s", "waiting_time_s" ], "type": "object" }, "UnassignedTask": { "description": "One unassigned task of an optimisation solution.", "properties": { "id": { "description": "The task id from the request.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "The task's location, when known." }, "type": { "description": "Task kind: \"job\", \"pickup\" or \"delivery\".", "type": "string" } }, "required": [ "id", "type" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "profile": { "description": "The matrix costing profile the plan was computed with (\"auto\",\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\").", "type": "string" }, "relaxation": { "description": "The relaxation report: the caps requested, whether a second solve\nran, whether ITS plan is the one returned, what was widened, and\nwhat is still unassigned. Present only when `relax_if_unassigned`\nwas declared.\n\nAlways read `second_solve` and `relaxed_plan_used` before telling\nanyone the day fits. A plan produced under relaxation has had\npromises moved, and the block is what says so." }, "reloads": { "description": "The multi-trip split: each vehicle's trips, their windows, the\ndepot each returns to, and the stated approximation. Present only\nwhen a vehicle declared `reloads`.\n\nRead the `basis` inside it before quoting arrival times. The trip\nwindows are fixed BEFORE the solve, so a lorry that tips early\ncannot lend the spare time to its next trip: stops can come back\nunassigned that a truly sequential model would have served. The\nplan is feasible, never optimistic — it cannot put a vehicle in two\nplaces at once — but it is not optimal. Re-plan after each tip\nthrough `replan_routes` for the tighter answer." }, "routes": { "description": "One optimised route per used vehicle.", "items": { "$ref": "#/$defs/OptimisedRoute" }, "type": "array" }, "summary": { "$ref": "#/$defs/OptimiseSummary", "description": "Solution summary." }, "territories": { "description": "How the territories bound the plan: which vehicle was eligible for\nwhat, and any task no eligible vehicle existed for. Present only\nwhen `territories` was declared." }, "unassigned": { "description": "Tasks the solver could not assign to any vehicle.", "items": { "$ref": "#/$defs/UnassignedTask" }, "type": "array" }, "zones": { "description": "Clean-air / low-emission zones touching the problem's own\nlocations, and what the declared vehicle pays in each. Present only\nwhen `emissions` was declared." } }, "required": [ "profile", "summary", "unassigned", "routes" ], "type": "object" } }, { "description": "Put a single run's stops in the best visiting order (\"order my errands\"). Provide `start` {lat, lon} and `stops` (1-100 entries of {location, label?, service_s?}); optionally an `end` destination or `round_trip`: true to return to the start. Costing \"auto\" = car, \"truck\" = lorry (pass `truck` as in `route` for a truck-legal order). Returns the stops in visit order with arrival offsets in seconds, plus total duration and distance.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OrderStopSpec": { "description": "One errand stop of an `order_stops` request.", "properties": { "label": { "description": "Human-readable label echoed back in the ordered plan\n(e.g. \"chemist\" or \"site B\").", "type": [ "string", "null" ] }, "location": { "$ref": "#/$defs/LatLon", "description": "The stop's location." }, "service_s": { "description": "On-site time in seconds (waiting, loading, shopping).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "location" ], "type": "object" }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model: \"auto\" (default) or \"truck\"." }, "end": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Optional fixed final destination. Mutually exclusive with\n`round_trip`; omitted (and not a round trip), the run ends at\nwhichever stop the solver visits last." }, "round_trip": { "default": false, "description": "Return to `start` after the last stop (default false).", "type": "boolean" }, "start": { "$ref": "#/$defs/LatLon", "description": "Where the run starts." }, "stops": { "description": "The stops to put in the best visiting order (1–100).", "items": { "$ref": "#/$defs/OrderStopSpec" }, "type": "array" }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration); requires costing\n\"truck\". The travel-time matrix then respects dimensional and\ndangerous-goods restrictions." } }, "required": [ "start", "stops" ], "type": "object" }, "name": "order_stops", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OrderedStop": { "description": "One stop of an ordered errand plan.", "properties": { "arrival_s": { "description": "Arrival time as an offset from departure, in seconds.", "format": "int64", "type": "integer" }, "label": { "description": "The request's label for this stop, when one was given.", "type": [ "string", "null" ] }, "location": { "$ref": "#/$defs/LatLon", "description": "The stop's location." }, "order": { "description": "1-based visit order.", "format": "uint32", "minimum": 0, "type": "integer" }, "stop_index": { "description": "The stop's 0-based index in the request's `stops` array.", "format": "uint32", "minimum": 0, "type": "integer" } }, "required": [ "order", "stop_index", "location", "arrival_s" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "distance_m": { "description": "Total travel distance in metres, when reported.", "format": "int64", "type": [ "integer", "null" ] }, "duration_s": { "description": "Total travel time in seconds.", "format": "int64", "type": "integer" }, "ordered": { "description": "The stops in optimal visiting order.", "items": { "$ref": "#/$defs/OrderedStop" }, "type": "array" }, "profile": { "description": "The matrix costing profile (\"auto\" or \"truck\").", "type": "string" }, "unassigned_stop_indexes": { "description": "0-based indexes of stops the solver could not fit (empty in the\nnormal, unconstrained case).", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": "array" } }, "required": [ "profile", "ordered", "unassigned_stop_indexes", "duration_s" ], "type": "object" } }, { "description": "What is over THERE: given a position and the direction the user is facing, the places inside that cone, and whether the ground and the buildings in between let them actually be seen. Use this for \"what is that over there\", \"is there a pub in that direction\", \"what am I looking at\". Use `nearby_places` instead when the question is about what is NEAR a point rather than what is in a direction. Provide `lat`, `lon`, `bearing_deg` (degrees CLOCKWISE FROM TRUE NORTH: 0 north, 90 east, 180 south, 270 west) and at least one of `category` or `name`; `category=building` reaches named buildings. Optional `fov_deg` is the FULL width of the cone (default 60, so 30 degrees either side), `radius_m` the range (default 1000, max 5000), `eye_height_m` the observer's eye height (default 1.6). Each result carries `distance_m`, `bearing_deg`, a signed `angular_offset_deg` (negative left, positive right), a spoken `direction`, and a `visibility` block. READ THE VISIBILITY BLOCK BEFORE SAYING ANYTHING: `clear` means nothing in the data stands in the way, `occluded` names what does (a hill, or a building with its height), and `unknown` means NO check could run: with `unknown` say you cannot tell, never that it is visible. Read `basis` too: `terrain-only` in a town means hills were checked and buildings were not, which is weak. A non-zero `out_of_sector` means there ARE matching places nearby, just not in that direction, so say that rather than \"nothing nearby\". `coverage` names the datasets that answered and `caveat` is the standing limit: visibility is modelled from maps, not observed, and trees, walls, scaffolding and weather are not in it. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY).", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "bearing_deg": { "description": "Which way the observer is facing, in degrees **clockwise from true\nnorth** (0 = north, 90 = east, 180 = south, 270 = west). Required:\nthis tool answers \"what is over there\", and \"there\" has to point\nsomewhere.", "format": "double", "type": "number" }, "category": { "description": "The kind of place to look for, from the same vocabulary\n`nearby_places` uses (\"cafe\", \"pub\", \"fuel\", \"charging_station\" …).\nUse \"building\" to ask what a named building over there is. At\nleast one of `category` and `name` is required.", "type": [ "string", "null" ] }, "eye_height_m": { "description": "Height of the observer's eye above the ground in metres (0–500,\ndefault 1.6, a standing adult). A driver's eye is nearer 1.2 m.", "format": "double", "type": [ "number", "null" ] }, "fov_deg": { "description": "Full width in degrees of the cone centred on `bearing_deg` (1–360,\ndefault 60, so 30 degrees either side). The FULL width, not the\nhalf angle.", "format": "double", "type": [ "number", "null" ] }, "lat": { "description": "Latitude of the observer in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "limit": { "description": "Maximum number of results (1–25, default 5).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "lon": { "description": "Longitude of the observer in decimal degrees (−180 to 180).", "format": "double", "type": "number" }, "name": { "description": "A place name or brand to look for. At least one of `category` and\n`name` is required.", "type": [ "string", "null" ] }, "radius_m": { "description": "Maximum straight-line range in metres (1–5000, default 1000).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "visible_only": { "description": "When true, return only places whose visibility verdict is `clear`.\nDefault false, which returns everything in the cone WITH its\nverdict, usually the better answer, because \"it is there but you\ncannot see it from here\" is worth saying.", "type": [ "boolean", "null" ] } }, "required": [ "lat", "lon", "bearing_deg" ], "type": "object" }, "name": "places_in_view", "outputSchema": { "$defs": { "PlaceInView": { "description": "One place in the observer's field of view.", "properties": { "angular_offset_deg": { "description": "Signed angle from the observer's bearing to the place: negative to\nthe left, positive to the right.", "format": "double", "type": "number" }, "bearing_deg": { "description": "The place's own bearing from the observer, degrees clockwise from\ntrue north.", "format": "double", "type": "number" }, "categories": { "default": [], "description": "The place's categories as the map tags them.", "items": { "type": "string" }, "type": "array" }, "direction": { "description": "The direction phrased for speech, from where the observer stands\n(\"ahead and slightly to your right, about 80 metres\"). Prefer\nreading this aloud over coordinates.", "type": "string" }, "distance_m": { "description": "Straight-line distance from the observer, in metres.", "format": "double", "type": "number" }, "label": { "description": "One-line label: name, street, postcode, town, country.", "type": "string" }, "lat": { "description": "Latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees.", "format": "double", "type": "number" }, "name": { "description": "Place name.", "type": "string" }, "visibility": { "$ref": "#/$defs/ViewVisibility", "description": "Whether it can be seen, and what that rests on." } }, "required": [ "name", "label", "lat", "lon", "distance_m", "bearing_deg", "angular_offset_deg", "direction", "visibility" ], "type": "object" }, "ViewObstruction": { "description": "What stands in the way of seeing a place.", "properties": { "building_height_basis": { "description": "`tagged` when somebody mapped the building's height, or\n`schema-default` when nobody did and the map's flat 5 m fallback\nwas used. A `schema-default` obstruction is a weaker finding and\nshould be described as one.", "type": [ "string", "null" ] }, "building_height_m": { "description": "Height of the blocking building above its own ground, in metres.\nAbsent for terrain.", "format": "double", "type": [ "number", "null" ] }, "building_name": { "description": "The blocking building's name, when the map carries one.", "type": [ "string", "null" ] }, "distance_m": { "description": "How far along the sight line it stands, in metres.", "format": "double", "type": "number" }, "height_above_sightline_m": { "description": "How far its top rises above the sight line, in metres.", "format": "double", "type": "number" }, "kind": { "description": "`terrain` or `building`.", "type": "string" } }, "required": [ "kind", "distance_m", "height_above_sightline_m" ], "type": "object" }, "ViewVisibility": { "description": "Whether a place can be seen from the observer, and what that rests on.", "properties": { "basis": { "description": "What the verdict rests on: `terrain-and-buildings`,\n`terrain-only`, `buildings-only` or `nothing`. `terrain-only` in a\ntown is weak: it means hills were checked and buildings were not.", "type": "string" }, "buildings": { "description": "What the building check concluded: `clear`, `occluded`,\n`no-building-data` or `beyond-building-range`.", "type": "string" }, "obstruction": { "anyOf": [ { "$ref": "#/$defs/ViewObstruction" }, { "type": "null" } ], "description": "The obstruction, present exactly when the verdict is `occluded`." }, "terrain": { "description": "What the terrain check concluded: `clear`, `occluded` or\n`no-elevation-data`.", "type": "string" }, "verdict": { "description": "`clear` (nothing in the data stands in the way), `occluded`\n(something does, and it is named), or `unknown` (no check could\nrun, so this result carries NO visibility claim: say so rather\nthan implying either).", "type": "string" } }, "required": [ "verdict", "basis", "terrain", "buildings" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "caveat": { "description": "The standing caveat: visibility here is modelled, not observed.", "type": "string" }, "coverage": { "description": "One sentence naming which datasets the visibility verdicts were\nactually checked against. Worth repeating when a verdict is being\nrelied on.", "type": "string" }, "out_of_sector": { "description": "How many otherwise-matching places were dropped for lying outside\nthe cone. Non-zero means there ARE matching places near the\nobserver, just not in the direction they are facing, so say that\nrather than \"nothing nearby\".", "format": "uint32", "minimum": 0, "type": "integer" }, "results": { "description": "Places inside the cone, best first: everything visible before\neverything unchecked before everything occluded, and within each\nof those, nearest and most nearly dead-ahead first.", "items": { "$ref": "#/$defs/PlaceInView" }, "type": "array" } }, "required": [ "results", "coverage", "caveat" ], "type": "object" } }, { "description": "Turn an itinerary into one navigable multi-stop route. Provide a `start` and `stops` (each a `location` {lat, lon} or a free-text `name` to geocode, plus optional `dwell_minutes` time at the stop), optional `depart_at` (RFC 3339) for absolute ETAs, `optimise: true` to reorder stops for the shortest day (VROOM solver), and `return_to_start`. Costing \"auto\", \"truck\" (with a `truck` profile the whole day respects dimensional/ADR restrictions), \"bicycle\", \"pedestrian\" or \"motor_scooter\". Returns the stops in visit order with per-leg duration/distance and arrival/departure times, totals, and the full route geometry (polyline6). Geocoded names carry a `resolution` — when `ambiguous` is true, check `alternatives` and re-run with an explicit location rather than trusting the guess.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "PlanDayStopInput": { "description": "One stop of a `plan_day` itinerary: an exact location, or a free-text\nname to geocode (ambiguous matches are flagged in the response, never\nguessed silently).", "properties": { "dwell_minutes": { "description": "Time spent at the stop in minutes (default 0); shifts every later\nETA.", "format": "double", "type": [ "number", "null" ] }, "location": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Exact coordinates; skips geocoding." }, "name": { "description": "Free-text name or address, geocoded when no `location` is given.", "type": [ "string", "null" ] } }, "type": "object" }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model: \"auto\" (default), \"truck\", \"bicycle\", \"pedestrian\"\nor \"motor_scooter\"." }, "depart_at": { "description": "Departure time as RFC 3339 (e.g. \"2026-07-18T09:00:00Z\"); when\ngiven, every ETA is also returned as an absolute timestamp.", "type": [ "string", "null" ] }, "optimise": { "default": false, "description": "Reorder the stops for the shortest day (VROOM solver; requires the\noptimisation sidecar). Default false: visit in the given order.", "type": "boolean" }, "return_to_start": { "default": false, "description": "End the day back at the start (default false).", "type": "boolean" }, "start": { "$ref": "#/$defs/PlanDayStopInput", "description": "Where the day starts (name or location; `dwell_minutes` ignored)." }, "stops": { "description": "The stops to visit (1–20). Visited in the given order unless\n`optimise` is true.", "items": { "$ref": "#/$defs/PlanDayStopInput" }, "type": "array" }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"." } }, "required": [ "start", "stops" ], "type": "object" }, "name": "plan_day", "outputSchema": { "$defs": { "GeocodeHit": { "description": "One geocoding result.", "properties": { "city": { "description": "City or town, when known.", "type": [ "string", "null" ] }, "country": { "description": "Country, when known.", "type": [ "string", "null" ] }, "label": { "description": "Human-readable one-line label assembled from the address parts.", "type": "string" }, "lat": { "description": "Latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees.", "format": "double", "type": "number" }, "match": { "anyOf": [ { "$ref": "#/$defs/GeocodeMatch" }, { "type": "null" } ], "description": "How far this hit can be trusted to be the place that was asked\nfor — see [`GeocodeMatch`]. Present whenever the MapMap gateway\nanswered; absent on a deployment falling back to the direct Photon\ngeocoder, and absent on the gateway's own fast paths (a pasted\ncoordinate pair, a bare UK outward code, a category browse), which\nanswer without a ranking to report on." }, "name": { "description": "Place name, when the source feature has one.", "type": [ "string", "null" ] }, "postcode": { "description": "Postcode, when known.", "type": [ "string", "null" ] }, "type": { "description": "Feature type, e.g. \"house\", \"street\", \"city\" (falls back to the\nOSM value when the endpoint does not classify).", "type": [ "string", "null" ] } }, "required": [ "label", "lat", "lon" ], "type": "object" }, "GeocodeMatch": { "description": "How well one geocoding result answers what was actually asked.\n\nGeocoding's real failure mode is not \"no answer\" but a confident answer\nto a different question: a plausible row on the wrong street, with\nnothing in the response to say so. This object is that missing say-so,\nand an agent should read it before acting on an address.\n\nHow to read it:\n\n* Any component `unmatched` or `inferred` on the TOP hit means the\n answer does not carry the address that was asked for — an `unmatched`\n postcode means the result has no postcode at all, `inferred` means it\n has a different one. Neither is a match. Say so rather than presenting\n the hit as the address, and reach for `verify_places` when the address\n came from a model or a user and needs checking rather than using.\n* A small `score_gap` means the ranking barely chose between this hit\n and the runner-up, which is exactly when to show the alternatives\n instead of picking one for the user.", "properties": { "components": { "$ref": "#/$defs/GeocodeMatchComponents", "description": "Per-component verdict on this hit: one entry for each structured\ncomponent supplied, and empty when the query was free text only." }, "score_gap": { "description": "The top result's score minus the runner-up's, rounded to 3 decimal\nplaces. `0` for a single result, and `0` from the `photon` source,\nwhich publishes no per-result score — so a `0` is \"no signal\", not\n\"a tie\".", "format": "double", "type": "number" }, "source": { "description": "Which backend answered: \"mapmap-index\" (the first-party index) or\n\"photon\".", "type": "string" } }, "required": [ "components", "score_gap", "source" ], "type": "object" }, "GeocodeMatchComponents": { "description": "Per-component verdicts inside a [`GeocodeMatch`]. Each is one of\n\"matched\", \"inferred\" or \"unmatched\"; a component that was not supplied\nis absent entirely.\n\n* \"matched\" — the result's own field carries the value asked for (case-\n and accent-insensitive, and by containment, so `city: \"London\"`\n matches \"City of London\").\n* \"inferred\" — the result carries a value for that component, but not\n the one asked for. It reached the page through ranking, as when a\n street is found by its transliterated name and displayed under its\n canonical one.\n* \"unmatched\" — the result carries no value for that component at all.", "properties": { "city": { "description": "Verdict on the supplied `city`.", "type": [ "string", "null" ] }, "country": { "description": "Verdict on the supplied `country`.", "type": [ "string", "null" ] }, "housenumber": { "description": "Verdict on the supplied `housenumber`.", "type": [ "string", "null" ] }, "postcode": { "description": "Verdict on the supplied `postcode`.", "type": [ "string", "null" ] }, "street": { "description": "Verdict on the supplied `street`.", "type": [ "string", "null" ] } }, "type": "object" }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "PlanStart": { "description": "The day's starting point as resolved.", "properties": { "location": { "$ref": "#/$defs/LatLon", "description": "The start's resolved coordinates." }, "name": { "description": "The start's name, when one was given.", "type": [ "string", "null" ] }, "resolution": { "anyOf": [ { "$ref": "#/$defs/StopResolution" }, { "type": "null" } ], "description": "Geocoding details when the start was given as a name." } }, "required": [ "location" ], "type": "object" }, "PlannedStop": { "description": "One planned stop with its leg and ETAs.", "properties": { "arrival_at": { "description": "Absolute arrival time (RFC 3339), when `depart_at` was given.", "type": [ "string", "null" ] }, "arrival_offset_s": { "description": "Arrival, as seconds after departure from the start.", "format": "double", "type": "number" }, "departure_at": { "description": "Absolute departure time (RFC 3339), when `depart_at` was given.", "type": [ "string", "null" ] }, "departure_offset_s": { "description": "Departure (arrival + dwell), as seconds after the day's start.", "format": "double", "type": "number" }, "dwell_minutes": { "description": "Dwell time applied at this stop, minutes.", "format": "double", "type": "number" }, "input_index": { "description": "Index of this stop in the request's `stops` array (visit order may\ndiffer when optimised).", "format": "uint", "minimum": 0, "type": "integer" }, "location": { "$ref": "#/$defs/LatLon", "description": "The stop's resolved coordinates." }, "name": { "description": "The stop's name (from the request or the geocoder).", "type": [ "string", "null" ] }, "resolution": { "anyOf": [ { "$ref": "#/$defs/StopResolution" }, { "type": "null" } ], "description": "Geocoding details when the stop was given as a name." }, "travel_distance_m": { "description": "Distance of the leg arriving at this stop, metres.", "format": "double", "type": "number" }, "travel_duration_s": { "description": "Travel time of the leg arriving at this stop, seconds.", "format": "double", "type": "number" } }, "required": [ "input_index", "location", "dwell_minutes", "travel_duration_s", "travel_distance_m", "arrival_offset_s", "departure_offset_s" ], "type": "object" }, "ReturnLeg": { "description": "The return leg of a `return_to_start` plan.", "properties": { "arrival_at": { "description": "Absolute arrival time (RFC 3339), when `depart_at` was given.", "type": [ "string", "null" ] }, "arrival_offset_s": { "description": "Arrival back at the start, seconds after departure.", "format": "double", "type": "number" }, "travel_distance_m": { "description": "Distance back to the start, metres.", "format": "double", "type": "number" }, "travel_duration_s": { "description": "Travel time back to the start, seconds.", "format": "double", "type": "number" } }, "required": [ "travel_duration_s", "travel_distance_m", "arrival_offset_s" ], "type": "object" }, "StopResolution": { "description": "How a free-text stop name was resolved to coordinates.", "properties": { "alternatives": { "description": "Up to three alternative matches, best first.", "items": { "$ref": "#/$defs/GeocodeHit" }, "type": "array" }, "ambiguous": { "description": "True when other plausible matches exist somewhere else — check\n`alternatives` and re-run with an explicit `location` if the\nchosen one is wrong.", "type": "boolean" }, "chosen": { "$ref": "#/$defs/GeocodeHit", "description": "The chosen match (best geocoder hit)." }, "query": { "description": "The name that was geocoded.", "type": "string" } }, "required": [ "query", "chosen", "ambiguous", "alternatives" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "costing": { "description": "The costing the plan was routed with.", "type": "string" }, "depart_at": { "description": "The departure time echoed back, when one was given.", "type": [ "string", "null" ] }, "finish_at": { "description": "Absolute end of the day (RFC 3339), when `depart_at` was given.", "type": [ "string", "null" ] }, "finish_offset_s": { "description": "End of the day (last arrival + dwell), seconds after departure.", "format": "double", "type": "number" }, "geometry_polyline6": { "description": "Full multi-stop route geometry (polyline6) — hand it to the map SDK\nor the `route` tool consumers directly.", "type": "string" }, "optimised": { "description": "Whether the stop order was optimised (VROOM) or kept as given.", "type": "boolean" }, "return_leg": { "anyOf": [ { "$ref": "#/$defs/ReturnLeg" }, { "type": "null" } ], "description": "The leg back to the start, when `return_to_start` was set." }, "start": { "$ref": "#/$defs/PlanStart", "description": "The day's starting point." }, "stops": { "description": "The stops in visit order, each with leg, ETAs and any geocoding\nresolution to double-check.", "items": { "$ref": "#/$defs/PlannedStop" }, "type": "array" }, "summary": { "description": "One-line human-readable summary of the day.", "type": "string" }, "total_distance_m": { "description": "Total travel distance, metres.", "format": "double", "type": "number" }, "total_dwell_s": { "description": "Total time at stops, seconds.", "format": "double", "type": "number" }, "total_travel_duration_s": { "description": "Total driving/travel time, seconds.", "format": "double", "type": "number" } }, "required": [ "costing", "optimised", "start", "stops", "total_travel_duration_s", "total_dwell_s", "total_distance_m", "finish_offset_s", "geometry_polyline6", "summary" ], "type": "object" } }, { "description": "Order a handful of errands against a hard arrival time, and say honestly whether they fit. This is the \"pick up a prescription, get petrol, and be at the school by quarter past three\" tool. Give `origin`, `destination` (+ `destination_name`), `arrive_by` (RFC 3339 with an offset: convert \"quarter past three\" yourself), optional `depart_at`, and 1 to 5 `errands`. Each errand is EITHER a `category` (a kind of place: \"pharmacy\", \"fuel\", \"supermarket\", or a colloquial phrase the server normalises; use `list_place_categories` for the vocabulary) OR a `place` {lat, lon} the driver already knows (\"the school\", \"the nursery\"). Resolve a named place with `geocode` first and pass the coordinate: never invent one. Optional `dwell_minutes` per errand (default 5). The server chooses the order and the actual shops, exhaustively, over engine-computed travel times -- do NOT attempt the ordering or the arithmetic yourself. IMPORTANT: `feasible: false` is an ANSWER, not an error to retry. It names the errand that costs the most (`blocking_errand`), says how late the whole chain would be (`over_by_s`), and returns the stops of the chain that DOES fit with `dropped` naming what had to go. Tell the driver what to drop. Every stop is a real indexed place carrying its id: never mention a shop the answer did not return. `hours` is \"open_on_the_tag\" (the map's tag covers your arrival, which is evidence and not a promise) or \"unknown\" (most places carry no hours at all); a place the tag proves shut is never proposed. ALWAYS show `usage_note`: this is planned before setting off or by a passenger, never at the wheel. Requires the MapMap gateway.", "inputSchema": { "$defs": { "ErrandSpec": { "description": "One errand in a `plan_errands` chain: a kind of place to find, or a\nplace the caller already knows.", "properties": { "category": { "description": "The KIND of place wanted: a category from `list_place_categories`\n(\"pharmacy\", \"fuel\", \"supermarket\"), or a colloquial phrase the\nserver normalises (\"petrol station\", \"chemist\"). Use this for\nanything the driver described by what it is rather than by name.", "type": [ "string", "null" ] }, "dwell_minutes": { "description": "How long the driver is out of the car here, minutes (default 5).", "format": "double", "type": [ "number", "null" ] }, "id": { "description": "What to call this errand in the answer (\"prescription\", \"petrol\").\nDefaults to the category, or to \"stop N\".", "type": [ "string", "null" ] }, "place": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "A specific place the driver already knows, as a coordinate. Use\nthis for \"the school\", \"the nursery\", \"Mum's\": places the map\ncannot be expected to resolve from the words alone. Resolve a NAMED\nplace with `geocode` first and pass the coordinate here; never\nguess one." } }, "type": "object" }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "arrive_by": { "description": "The hard arrival time, RFC 3339 with an offset\n(\"2026-09-21T15:15:00+01:00\"). Convert \"quarter past three\" to an\nabsolute instant in the driver's own offset before calling.", "type": "string" }, "depart_at": { "description": "When they set off, RFC 3339. Defaults to now.", "type": [ "string", "null" ] }, "destination": { "$ref": "#/$defs/LatLon", "description": "Where they have to be by `arrive_by`." }, "destination_name": { "description": "What to call the destination in the answer (\"the school\").", "type": [ "string", "null" ] }, "errands": { "description": "The errands, in any order: the answer decides the order. One to\nfive.", "items": { "$ref": "#/$defs/ErrandSpec" }, "type": "array" }, "max_detour_minutes": { "description": "How far off the direct route a candidate place may sit, as a detour\nin minutes (default 12, at most 45).", "format": "double", "type": [ "number", "null" ] }, "origin": { "$ref": "#/$defs/LatLon", "description": "Where the driver sets off from." } }, "required": [ "origin", "destination", "arrive_by", "errands" ], "type": "object" }, "name": "plan_errands", "outputSchema": { "$defs": { "ErrandStop": { "description": "One planned stop in an errand chain.", "properties": { "arrive": { "description": "When the driver arrives, RFC 3339.", "type": "string" }, "depart": { "description": "When they leave, RFC 3339.", "type": "string" }, "drive_s": { "description": "Driving time of the leg into this stop, seconds.", "format": "double", "type": "number" }, "errand": { "description": "Which errand this stop discharges.", "type": "string" }, "hours": { "description": "What the map's opening-hours tag says about the arrival time:\n\"open_on_the_tag\" (the tag covers it, which is evidence and NOT a\npromise) or \"unknown\" (no usable tag). A stop the tag proves shut\nis never proposed, so \"closed\" never appears here.", "type": "string" }, "hours_tag": { "description": "The tag itself, when the map carries one.", "type": [ "string", "null" ] }, "id": { "description": "The index id, for an indexed place. Its presence is what makes the\nstop checkable; a stop without one is a coordinate the caller gave.", "type": [ "string", "null" ] }, "kind": { "description": "\"category\" for a place the index found, \"place\" for one the caller\nsupplied.", "type": "string" }, "label": { "description": "Its full label, for reading aloud.", "type": [ "string", "null" ] }, "lat": { "description": "Latitude.", "format": "double", "type": "number" }, "lon": { "description": "Longitude.", "format": "double", "type": "number" }, "name": { "description": "The place's name.", "type": [ "string", "null" ] } }, "required": [ "errand", "kind", "lat", "lon", "arrive", "depart", "drive_s", "hours" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "arrival": { "description": "When the plan arrives, RFC 3339. Present whenever there is a plan,\nincluding the reduced plan on an infeasible answer.", "type": [ "string", "null" ] }, "arrive_by": { "description": "The deadline, echoed.", "type": "string" }, "attribution": { "description": "Credit owed for the place data, when the answer used the index.", "type": [ "string", "null" ] }, "blocking_errand": { "description": "The errand that costs the most time, when the deadline is missed.", "type": [ "string", "null" ] }, "corridor_note": { "description": "**Always present.** What the corridor search did and did not look\nat. An empty answer is a statement about this index and this\ncorridor, never about what exists on the ground.", "type": "string" }, "departure": { "description": "When the driver sets off, RFC 3339.", "type": "string" }, "dropped": { "description": "Errands that had to be dropped for the plan to fit. Empty when\nfeasible.", "items": { "type": "string" }, "type": "array" }, "failed_errand": { "description": "The errand a \"no_candidates_in_corridor\" or \"all_candidates_closed\"\nanswer is about.", "type": [ "string", "null" ] }, "feasible": { "description": "**Whether every errand fits before the deadline.** False is an\nANSWER, not an error: report `reason`, the errand named in\n`blocking_errand`, and what `dropped` says has to go.", "type": "boolean" }, "geometry_polyline6": { "description": "The planned journey as an encoded polyline6.", "type": [ "string", "null" ] }, "opening_hours_note": { "description": "**Always present.** How opening hours were used, and how little of\nthe map carries them.", "type": "string" }, "over_by_s": { "description": "How late the whole chain would be, seconds.", "format": "double", "type": [ "number", "null" ] }, "reason": { "description": "The same cause in plain language, for the driver.", "type": [ "string", "null" ] }, "reason_code": { "description": "Machine token for why the chain does not fit: \"deadline_missed\",\n\"destination_unreachable_in_time\", \"no_candidates_in_corridor\",\n\"all_candidates_closed\" or \"unroutable\".", "type": [ "string", "null" ] }, "slack_s": { "description": "Spare seconds before the deadline.", "format": "double", "type": [ "number", "null" ] }, "stops": { "description": "The stops, in visit order. On an infeasible answer these are the\nstops of the reduced plan, the one that DOES fit.", "items": { "$ref": "#/$defs/ErrandStop" }, "type": "array" }, "total_drive_s": { "description": "Total driving time, seconds.", "format": "double", "type": [ "number", "null" ] }, "total_dwell_s": { "description": "Total time out of the car, seconds.", "format": "double", "type": [ "number", "null" ] }, "usage_note": { "description": "**Always present.** Why this is planned stationary rather than at\nthe wheel. Show it.", "type": "string" }, "verification_note": { "description": "**Always present.** That every stop is an indexed place or a\nsupplied coordinate, never a generated one.", "type": "string" } }, "required": [ "feasible", "departure", "arrive_by", "stops", "dropped", "corridor_note", "opening_hours_note", "usage_note", "verification_note" ], "type": "object" } }, { "description": "Plan a whole electric-vehicle journey, charge stops included. Give `origin` and `destination` (plus optional `waypoints`) and a `vehicle` — a published profile (\"small_hatch\", \"saloon\", \"suv\", \"van\") and/or inline figures (battery_kwh, mass_kg, drag_area_m2, aux_kw, connectors) — with `start_soc` (default 0.9), `min_arrival_soc` (default 0.1), `reserve_soc` (default 0.1, the floor the charge must never drop below mid-route), optional `connectors` and `min_kw` filters and `ambient_temperature_c`. Energy comes from a published road-load physics model over the route's own legs; charge times are integrated over the vehicle's charging curve capped by the charge point, NOT energy divided by peak power, which is the single biggest error in naive EV planners. Returns the stops with arrive/depart state of charge, charge time and detour, a per-leg state-of-charge trace, and the journey's driving and charging time. IMPORTANT: when no plan exists — a charger desert, a connector mismatch, a gap wider than the car's range — the answer comes back with `feasible: false`, a `reason` and the furthest point on the route the car can actually reach. That is an ANSWER, not an error to retry: report the reason and never describe it as a plan. `gradient_data` says whether elevation was available: \"absent\" means consumption was modelled on the flat and under-reads a hilly route. There is no national charge-point registry, so ALWAYS show the returned `coverage_note` — an infeasible plan means \"none from these operators\", never \"there are no chargers here\" — and statuses are current only when `availability_live` is true. Requires the MapMap gateway; answers a clear error when the deployment has no charge-point dataset. Display the returned charging_attribution with the plan.", "inputSchema": { "$defs": { "EvVehicleSpec": { "description": "The vehicle for `plan_ev_route`: one of the published defaults by name,\ninline figures, or a named default with inline figures over it.", "properties": { "aux_kw": { "description": "Auxiliary load in kW — lights, electronics, cabin conditioning.\nWinter heating is several times the mild-weather default.", "format": "double", "type": [ "number", "null" ] }, "battery_kwh": { "description": "Gross nominal battery capacity, kWh.", "format": "double", "type": [ "number", "null" ] }, "connectors": { "description": "Connectors the vehicle accepts: \"type1\", \"type2\", \"ccs1\", \"ccs2\",\n\"chademo\", \"tesla\", \"gbt\".", "items": { "type": "string" }, "type": [ "array", "null" ] }, "drag_area_m2": { "description": "Drag area (drag coefficient × frontal area), m².", "format": "double", "type": [ "number", "null" ] }, "mass_kg": { "description": "Kerb mass, kg.", "format": "double", "type": [ "number", "null" ] }, "name": { "description": "Display name for the vehicle in the answer.", "type": [ "string", "null" ] }, "profile": { "description": "A published default profile: \"small_hatch\", \"saloon\", \"suv\" or\n\"van\". On its own it selects that vehicle; alongside any inline\nfigure below it is the base the figure overrides.", "type": [ "string", "null" ] }, "usable_fraction": { "description": "Fraction of the gross pack the vehicle will actually use, 0–1.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "ambient_temperature_c": { "description": "Ambient temperature in °C. Derates traction energy from a published\nstudy; cabin heating belongs in the vehicle's `aux_kw`.", "format": "double", "type": [ "number", "null" ] }, "connectors": { "description": "Keep only charge points offering at least one of these connector\nstandards: \"type2\", \"type1\", \"ccs\", \"chademo\", \"tesla\", \"domestic\",\n\"other\". This narrows the vehicle's own set, never widens it.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "destination": { "$ref": "#/$defs/LatLon", "description": "Where it ends." }, "max_detour_minutes": { "description": "How far off the route a charge point may sit, as a detour in\nminutes (default 15, at most 120).", "format": "double", "type": [ "number", "null" ] }, "min_arrival_soc": { "description": "Lowest acceptable state of charge on arrival (default 0.1).", "format": "double", "type": [ "number", "null" ] }, "min_kw": { "description": "Keep only charge points with a usable connector rated at least this\nmany kW (e.g. 50 for rapid charging only).", "format": "double", "type": [ "number", "null" ] }, "origin": { "$ref": "#/$defs/LatLon", "description": "Where the journey starts." }, "reserve_soc": { "description": "The floor the state of charge must never fall below mid-route\n(default 0.1). Distinct from the arrival figure.", "format": "double", "type": [ "number", "null" ] }, "start_soc": { "description": "State of charge at the start, 0–1 (default 0.9).", "format": "double", "type": [ "number", "null" ] }, "vehicle": { "anyOf": [ { "$ref": "#/$defs/EvVehicleSpec" }, { "type": "null" } ], "description": "The vehicle. Omitted ⇒ the published \"saloon\" default, and the\nanswer says which vehicle it used." }, "waypoints": { "description": "Intermediate points the route must pass through, in order (at most\n8). Charge stops are inserted around them.", "items": { "$ref": "#/$defs/LatLon" }, "type": [ "array", "null" ] } }, "required": [ "origin", "destination" ], "type": "object" }, "name": "plan_ev_route", "outputSchema": { "$defs": { "EvPlanLeg": { "description": "One driving leg of the plan, with its state-of-charge bookkeeping.", "properties": { "consumed_wh": { "description": "Battery energy the leg spends, Wh.", "format": "double", "type": "number" }, "distance_m": { "description": "Engine-computed distance, metres.", "format": "double", "type": "number" }, "duration_s": { "description": "Engine-computed driving time, seconds.", "format": "double", "type": "number" }, "end_soc": { "description": "State of charge at the end.", "format": "double", "type": "number" }, "from": { "description": "Where the leg starts: \"origin\" or a charge point's id.", "type": "string" }, "min_soc": { "description": "Lowest state of charge anywhere within the leg.", "format": "double", "type": [ "number", "null" ] }, "regen_wh": { "description": "Battery energy regenerative braking gives back, Wh.", "format": "double", "type": "number" }, "start_soc": { "description": "State of charge at the start of the leg.", "format": "double", "type": "number" }, "to": { "description": "Where it ends: a charge point's id or \"destination\".", "type": "string" } }, "required": [ "from", "to", "duration_s", "distance_m", "start_soc", "end_soc", "consumed_wh", "regen_wh" ], "type": "object" }, "EvPlanSocPoint": { "description": "One point on the state-of-charge trace.", "properties": { "along_route_position": { "description": "How far along the route this point sits, 0.0–1.0.", "format": "double", "type": "number" }, "at": { "description": "\"origin\", a charge point's id, or \"destination\".", "type": "string" }, "departing_soc": { "description": "State of charge on leaving, at a charge stop.", "format": "double", "type": [ "number", "null" ] }, "soc": { "description": "State of charge on arrival at this point.", "format": "double", "type": "number" } }, "required": [ "at", "soc", "along_route_position" ], "type": "object" }, "EvPlanStop": { "description": "One charge stop in the plan.", "properties": { "along_route_position": { "description": "How far along the route this stop sits, 0.0–1.0.", "format": "double", "type": "number" }, "arrive_soc": { "description": "State of charge on arrival. Never below the reserve floor.", "format": "double", "type": "number" }, "available_now": { "description": "Whether a bay is free right now. Present only where a live\navailability feed backs the claim — absent means unknown, never\n\"occupied\".", "type": [ "boolean", "null" ] }, "charge_s": { "description": "Time plugged in, seconds — integrated over the vehicle's charging\ncurve capped by the charge point, not energy ÷ peak power.", "format": "double", "type": "number" }, "charger_id": { "description": "Operator-scoped stable id.", "type": "string" }, "charger_kw": { "description": "That connector's rated power, kW — what the charge time was\ncomputed against.", "format": "double", "type": [ "number", "null" ] }, "connector_standard": { "description": "The connector the plan charges on.", "type": [ "string", "null" ] }, "dc": { "description": "Whether the connector delivers DC (rapid) rather than AC.", "type": "boolean" }, "depart_soc": { "description": "State of charge on departure.", "format": "double", "type": "number" }, "detour_s": { "description": "Extra driving time this stop costs against going straight past it,\nseconds.", "format": "double", "type": [ "number", "null" ] }, "lat": { "description": "WGS84 latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "WGS84 longitude in decimal degrees.", "format": "double", "type": "number" }, "name": { "description": "Site name, where the operator publishes one.", "type": [ "string", "null" ] }, "off_route_m": { "description": "Straight-line offset from the route geometry, metres.", "format": "double", "type": "number" }, "operator": { "description": "Operator display name.", "type": [ "string", "null" ] }, "power_kw_source": { "description": "\"declared\" when the operator published the rating, \"derived\" when it\nwas computed from voltage × amperage × phases. Never present the two\nas the same thing to a user.", "type": [ "string", "null" ] }, "source": { "description": "Which operator feed this came from.", "type": "string" }, "status_live": { "description": "Whether the status came from a live feed rather than the last\ningest.", "type": "boolean" } }, "required": [ "charger_id", "source", "lat", "lon", "dc", "arrive_soc", "depart_soc", "charge_s", "along_route_position", "off_route_m", "status_live" ], "type": "object" }, "EvPlanSummary": { "description": "The plan at a glance.", "properties": { "arrival_soc": { "description": "State of charge on arrival, when the journey is feasible.", "format": "double", "type": [ "number", "null" ] }, "energy_kwh": { "description": "Net battery energy the journey costs, kWh.", "format": "double", "type": "number" }, "start_soc": { "description": "State of charge the journey starts at.", "format": "double", "type": "number" }, "stops": { "description": "How many charge stops the plan contains. Zero on a journey the car\nmakes on its own — and zero on an infeasible one.", "format": "uint", "minimum": 0, "type": "integer" }, "total_charge_s": { "description": "Time spent plugged in, seconds.", "format": "double", "type": "number" }, "total_distance_m": { "description": "Distance of the planned journey, metres.", "format": "double", "type": "number" }, "total_drive_s": { "description": "Time spent driving, seconds.", "format": "double", "type": "number" }, "total_duration_s": { "description": "Driving plus charging, seconds — the number a user cares about.", "format": "double", "type": "number" } }, "required": [ "stops", "total_drive_s", "total_charge_s", "total_duration_s", "total_distance_m", "start_soc", "energy_kwh" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "availability_live": { "description": "Whether charge-point statuses came from a live availability feed. A\nstatic planner is the default: without a feed, nothing in this\nanswer is a claim about which bays are free right now.", "type": "boolean" }, "charging_attribution": { "description": "Attribution string for the charge-point operators actually used —\ndisplay it with the plan (a licence obligation).", "type": [ "string", "null" ] }, "coverage_note": { "description": "**Always present.** What this deployment's charge-point dataset does\nand does not cover. An infeasible plan means \"none from these\noperators\", never \"there are no chargers here\". Show this alongside\nthe answer.", "type": "string" }, "feasible": { "description": "**Whether the journey is possible at all.** False means no plan\nexists — a charger desert, a connector mismatch, or a gap wider than\nthe car's range. Report the `reason` and the furthest reachable\npoint; never describe an infeasible answer as a plan.", "type": "boolean" }, "furthest_reachable_lat": { "description": "Latitude of that furthest reachable point.", "format": "double", "type": [ "number", "null" ] }, "furthest_reachable_lon": { "description": "Longitude of that furthest reachable point.", "format": "double", "type": [ "number", "null" ] }, "furthest_reachable_position": { "description": "How far along the route the vehicle can get unaided, 0.0–1.0, when\nno plan exists.", "format": "double", "type": [ "number", "null" ] }, "geometry_polyline6": { "description": "The planned journey's geometry as an encoded polyline6, through the\ncharge stops.", "type": [ "string", "null" ] }, "gradient_data": { "description": "\"complete\", \"partial\" or \"absent\". **\"absent\" means the deployment\nhad no elevation data and consumption was modelled on the flat**,\nwhich under-reads a hilly route. Say so rather than presenting the\nfigure as measured.", "type": "string" }, "legs": { "description": "The driving legs, in order.", "items": { "$ref": "#/$defs/EvPlanLeg" }, "type": "array" }, "profile_source": { "description": "\"default\" when a published profile supplied the figures, \"inline\"\nwhen the caller did.", "type": [ "string", "null" ] }, "reason": { "description": "The same cause in plain language, for the user.", "type": [ "string", "null" ] }, "reason_code": { "description": "Machine token for why no plan exists, when none does:\n\"no_chargers_in_corridor\", \"connector_mismatch\", \"out_of_range\",\n\"dead_end\", \"below_min_kw\", \"chargers_unrated\", \"stop_limit\",\n\"dataset_empty\", \"no_charge_curve\" or \"unroutable\".", "type": [ "string", "null" ] }, "route_distance_m": { "description": "Distance of the planned route, metres.", "format": "double", "type": [ "number", "null" ] }, "route_duration_s": { "description": "Driving time of the planned route, seconds.", "format": "double", "type": [ "number", "null" ] }, "soc_trace": { "description": "State of charge at every point of the journey.", "items": { "$ref": "#/$defs/EvPlanSocPoint" }, "type": "array" }, "stops": { "description": "The charge stops, in visit order. Always empty when `feasible` is\nfalse: a journey that cannot be completed has no stop list.", "items": { "$ref": "#/$defs/EvPlanStop" }, "type": "array" }, "summary": { "$ref": "#/$defs/EvPlanSummary", "description": "The plan at a glance." }, "vehicle": { "description": "The vehicle the plan was computed for.", "type": [ "string", "null" ] } }, "required": [ "feasible", "summary", "stops", "legs", "soc_trace", "gradient_data", "coverage_note", "availability_live" ], "type": "object" } }, { "description": "Compute the area reachable from an origin within one or more travel-time budgets — walkability/cyclability rings. Costing \"pedestrian\" answers \"how far can I walk in 15 minutes?\", \"bicycle\" the cycling equivalent; \"auto\", \"truck\" and \"motor_scooter\" work too (e.g. delivery coverage). `contours_minutes` lists the ring boundaries in minutes (1-10 values, each up to 120); set `polygons` true for filled polygons ready to render as a map fill layer instead of contour lines. Returns a GeoJSON FeatureCollection, one feature per contour. Optional `exclude_polygons` for before/after scenarios (\"close this bridge and recompute reachability\"): an array of polygons, each an array of [lon, lat] pairs forming one ring — longitude FIRST — whose intersecting roads are excluded from the reachability search. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "contours_minutes": { "description": "Contour boundaries in minutes of travel time, e.g. [5, 10, 15]\nfor 5/10/15-minute rings. 1–10 values, each between 0 and 120\nminutes.", "items": { "format": "double", "type": "number" }, "type": "array" }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Travel mode: \"auto\" (default), \"truck\", \"bicycle\", \"pedestrian\"\nor \"motor_scooter\"." }, "exclude_polygons": { "description": "Areas to avoid — scenario analysis (\"close this bridge and\nrecompute reachability\"): an array of polygons, each an array of\n`[lon, lat]` pairs forming one exterior ring (GeoJSON-style,\nlongitude FIRST). Roads intersecting any ring are excluded from\nthe reachability search. Applies to the Valhalla engine;\nunsupported on the GraphHopper engine, where it is ignored.", "items": { "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" }, "type": [ "array", "null" ] }, "origin": { "$ref": "#/$defs/LatLon", "description": "Origin the reachable area is computed from." }, "polygons": { "description": "Return filled polygons instead of contour linestrings (default\nfalse). Polygons draw directly as a MapLibre fill layer.", "type": [ "boolean", "null" ] } }, "required": [ "origin", "contours_minutes" ], "type": "object" }, "name": "reachable_area", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "geojson": { "description": "GeoJSON FeatureCollection of the reachability contours, one\nfeature per requested minute value (each feature's `contour`\nproperty is its minutes), as returned by the routing engine." } }, "required": [ "geojson" ], "type": "object" } }, { "description": "Re-plan a fleet part-way through its shift. Send the day back; nothing is stored. MapMap holds NO dispatch state — no plan, no vehicle position, no completion log — so a re-plan is not a delta against something we remember: you pass the ORIGINAL `optimise_routes` problem in full, plus `progress` (per vehicle: `completed_stop_ids` IN THE ORDER SERVED, an optional `current_position`, and `unavailable: true` for a breakdown or an end of hours) and/or `changes` (`cancel_job_ids`, `add_jobs`, `add_shipments`), and get a fresh plan for what is left. That costs bandwidth and buys the absence of a server-side plan that can go stale, leak, or fall out of step with the telematics platform that actually owns the truth — and it makes a re-plan reproducible: the same body always yields the same answer. Completed stops are LOCKED by construction: they are removed from the problem entirely and each vehicle starts from where it actually is, so the solver cannot move a stop that has already happened — a guarantee the solver cannot break, rather than a hint it is free to ignore. At least one progress entry or one change is required. Returns the same plan shape as `optimise_routes` for the REMAINING work, plus a `replan` block: the prefix locked per vehicle, where each re-plans from and how that was decided, stops released from a vehicle that can no longer serve them, tasks forced unassigned, and a note for every id that did not resolve. Read that block — a stop that vanished from the plan is named there rather than left for a dispatcher to notice at four in the afternoon; a cancelled id that matched nothing is reported there too rather than refused. Multi-trip vehicles cannot be re-planned: a completion does not say which trip it belongs to, so a problem whose vehicles declare `reloads` is refused with what to send instead. Billed on the remaining problem, not the original. Requires the MapMap gateway.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "EmissionsFuel": { "description": "What a vehicle burns, in clean-air-zone scheme terms.", "oneOf": [ { "const": "petrol", "description": "Petrol, including petrol hybrids (schemes rate a hybrid by its\ncombustion engine's approval).", "type": "string" }, { "const": "diesel", "description": "Diesel, including diesel hybrids.", "type": "string" }, { "const": "electric", "description": "Battery-electric.", "type": "string" }, { "const": "hydrogen", "description": "Hydrogen fuel cell.", "type": "string" }, { "const": "gas", "description": "LPG or CNG; rated as petrol by every scheme in the dataset.", "type": "string" } ] }, "EmissionsSpec": { "description": "A vehicle's emission declaration, for clean-air / low-emission zone\nassessment.", "properties": { "euro_standard": { "description": "Its Euro emission standard, 1–6. Heavy-duty approvals are written\nin Roman numerals (Euro VI); declare Euro VI as `6`. Required for\nany combustion fuel — without it no zone can be resolved, and a\nhalf-declared vehicle is indistinguishable from an undeclared one.\nOptional only for `electric` or `hydrogen`.", "format": "uint8", "maximum": 255, "minimum": 0, "type": [ "integer", "null" ] }, "fuel": { "$ref": "#/$defs/EmissionsFuel", "description": "What it burns." }, "vehicle_category": { "$ref": "#/$defs/EmissionsVehicleCategory", "description": "What kind of vehicle this is, in scheme terms." } }, "required": [ "vehicle_category", "fuel" ], "type": "object" }, "EmissionsVehicleCategory": { "description": "What a vehicle is, in clean-air-zone scheme terms.\n\nDeclaring this turns \"charge depends on vehicle emissions\" into an\nanswer. Without it a zone can only be named, never priced.", "oneOf": [ { "const": "car", "description": "A private car.", "type": "string" }, { "const": "van", "description": "A van or light goods vehicle up to 3.5 tonnes.", "type": "string" }, { "const": "minibus", "description": "A minibus (typically 8+ passenger seats, up to 5 tonnes).", "type": "string" }, { "const": "hgv", "description": "A heavy goods vehicle over 3.5 tonnes.", "type": "string" }, { "const": "bus", "description": "A bus over 5 tonnes.", "type": "string" }, { "const": "coach", "description": "A coach over 5 tonnes.", "type": "string" }, { "const": "taxi", "description": "A licensed hackney carriage.", "type": "string" }, { "const": "phv", "description": "A private hire vehicle.", "type": "string" }, { "const": "motorcycle", "description": "A motorcycle, moped or tricycle.", "type": "string" }, { "const": "motorhome", "description": "A motor caravan or campervan.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OptimiseJobSpec": { "description": "One single-stop job of an optimisation problem.", "properties": { "delivery": { "description": "Quantities delivered to the job (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "id": { "description": "Caller-chosen job id, echoed back in steps and `unassigned`.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "$ref": "#/$defs/LatLon", "description": "Job location." }, "pickup": { "description": "Quantities picked up at the job (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "service_s": { "description": "On-site service time in seconds.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "skills": { "description": "Skills the job requires.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] }, "time_windows": { "description": "Acceptable `[start, end]` windows in seconds.", "items": { "items": { "format": "int64", "type": "integer" }, "type": "array" }, "type": [ "array", "null" ] } }, "required": [ "id", "location" ], "type": "object" }, "OptimiseShipmentSpec": { "description": "A pickup+delivery pair that must ride the same vehicle, pickup first.", "properties": { "amount": { "description": "Quantities moved (matches vehicle `capacity`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "delivery": { "$ref": "#/$defs/OptimiseShipmentStopSpec", "description": "The delivery end." }, "pickup": { "$ref": "#/$defs/OptimiseShipmentStopSpec", "description": "The pickup end." }, "skills": { "description": "Skills the shipment requires.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] } }, "required": [ "pickup", "delivery" ], "type": "object" }, "OptimiseShipmentStopSpec": { "description": "One end (pickup or delivery) of a shipment.", "properties": { "id": { "description": "Caller-chosen stop id, echoed back in steps and `unassigned`.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "$ref": "#/$defs/LatLon", "description": "Stop location." }, "service_s": { "description": "On-site service time in seconds.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "id", "location" ], "type": "object" }, "OptimiseVehicleSpec": { "description": "One vehicle of an optimisation fleet.", "properties": { "capacity": { "description": "Multidimensional capacity (same length as job `delivery`/`pickup`).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "end": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "End location; omitted, the route ends at its last stop." }, "id": { "description": "Caller-chosen vehicle id, echoed back on its route.", "format": "uint64", "minimum": 0, "type": "integer" }, "reloads": { "anyOf": [ { "$ref": "#/$defs/ReloadsSpec" }, { "type": "null" } ], "description": "Let this vehicle return to a depot, reload and go out again — the\nwaste-collection tipping round, the van that comes back for a\nsecond wave of parcels." }, "skills": { "description": "Skills this vehicle provides.", "items": { "format": "uint32", "minimum": 0, "type": "integer" }, "type": [ "array", "null" ] }, "start": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Start location; at least one of `start`/`end` is required." }, "territory_ids": { "description": "Ids of the request's `territories` this vehicle may work in.\nOmitted or empty, the vehicle is UNRESTRICTED and may serve any\ntask, inside a territory or outside every one of them. Listed, the\nvehicle may serve a task only if that task sits inside at least one\nof the named territories.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "time_window": { "description": "Working window as `[start, end]` in seconds (any consistent epoch).", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] } }, "required": [ "id" ], "type": "object" }, "RelaxSpec": { "description": "What the caller is willing to give up if the first solve leaves work\nunassigned. At least one field is required.", "properties": { "allow_overtime_s": { "description": "Extend every vehicle's shift END by this many seconds. Shift starts\nare never moved earlier — a driver cannot begin before they begin.", "format": "int64", "type": [ "integer", "null" ] }, "time_windows_by_s": { "description": "Widen every task time window by this many seconds at EACH end. A\n09:00–12:00 window with 1800 becomes 08:30–12:30.", "format": "int64", "type": [ "integer", "null" ] } }, "type": "object" }, "ReloadsSpec": { "description": "A vehicle's multi-trip reload plan.", "properties": { "depot": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Where the vehicle reloads. Omitted, its own `start` is used (or its\n`end` if it declared only that)." }, "max_trips": { "description": "How many trips this vehicle may run in its shift, 2–5. A BUDGET,\nnot a prediction: the shift is cut into that many fixed windows\nbefore the solve, so asking for five trips on a shift that supports\nthree shrinks every window to a fifth and can make the whole day\nworse. Ask for the number of trips you actually expect to run.", "format": "uint32", "minimum": 0, "type": "integer" }, "reload_time_s": { "description": "Seconds at the depot between trips — tipping, reloading, the\nweighbridge. Held out of the shift before it is partitioned, so it\nis never accidentally spent driving.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "max_trips" ], "type": "object" }, "ReplanChangesSpec": { "description": "What has changed about the work since the plan was made.", "properties": { "add_jobs": { "description": "New single-stop jobs, in exactly the `optimise_routes` job shape.", "items": { "$ref": "#/$defs/OptimiseJobSpec" }, "type": "array" }, "add_shipments": { "description": "New pickup+delivery pairs, in exactly the `optimise_routes`\nshipment shape.", "items": { "$ref": "#/$defs/OptimiseShipmentSpec" }, "type": "array" }, "cancel_job_ids": { "description": "Ids of jobs that no longer need doing. A job already reported\ncompleted cannot be cancelled; the response says so rather than\nsilently dropping it.", "items": { "format": "uint64", "minimum": 0, "type": "integer" }, "type": "array" } }, "type": "object" }, "ReplanProgressSpec": { "description": "One vehicle's progress through its shift.", "properties": { "completed_stop_ids": { "description": "Ids of the stops this vehicle has already served, IN THE ORDER IT\nSERVED THEM. Each is a job id or a shipment pickup/delivery id.\nThis is the locked prefix: it already happened, so no re-plan may\nmove it.", "items": { "format": "uint64", "minimum": 0, "type": "integer" }, "type": "array" }, "current_position": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Where the vehicle is now; becomes its start for the re-plan.\nOmitted, its last completed stop is used; with neither, its\noriginal start." }, "unavailable": { "description": "The vehicle has dropped out of the shift — breakdown, illness, end\nof hours. It leaves the fleet for the re-plan; what it already\ncompleted stays completed.", "type": [ "boolean", "null" ] }, "vehicle": { "description": "The vehicle this progress belongs to — an `id` from `vehicles`.", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "vehicle" ], "type": "object" }, "TerritorySpec": { "description": "One named territory: a polygon that bounds which vehicle may serve\nwhich stop.", "properties": { "id": { "description": "Caller-chosen id, echoed back and referenced by\n`vehicles[].territory_ids`. Must be unique within the request.", "type": "string" }, "polygon": { "description": "The outer ring as GeoJSON `[lon, lat]` positions — longitude\nFIRST. Closed or open; an unclosed ring is closed for you.", "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" } }, "required": [ "id", "polygon" ], "type": "object" }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "avoid_zones": { "description": "Keep the optimisation's travel-time matrix out of every zone the\ndeclared vehicle would be charged or banned in. Requires\n`emissions`.", "type": [ "boolean", "null" ] }, "changes": { "anyOf": [ { "$ref": "#/$defs/ReplanChangesSpec" }, { "type": "null" } ], "description": "Changes to the work itself. Optional, on the same condition." }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model for the travel-time matrix: \"auto\" (default),\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\"." }, "emissions": { "anyOf": [ { "$ref": "#/$defs/EmissionsSpec" }, { "type": "null" } ], "description": "The fleet's emission declaration, for UK clean-air / low-emission\nzone assessment. On its own it annotates: the response's `zones`\nblock names every zone containing one of the problem's own\nlocations and what this vehicle would pay there. With\n`avoid_zones` it also steers the internal travel-time matrix away\nfrom those zones, so the plan itself changes." }, "jobs": { "description": "Single-stop jobs (at least one job or shipment overall).", "items": { "$ref": "#/$defs/OptimiseJobSpec" }, "type": "array" }, "progress": { "description": "Per-vehicle progress. Optional, but at least one progress entry or\none change is required — a re-plan that reports nothing new is the\noriginal problem.", "items": { "$ref": "#/$defs/ReplanProgressSpec" }, "type": "array" }, "relax_if_unassigned": { "anyOf": [ { "$ref": "#/$defs/RelaxSpec" }, { "type": "null" } ], "description": "Re-solve ONCE with these relaxations if the first solve leaves work\nunassigned, and say honestly which plan came back. At most one\nsecond solve, never beyond the caps you state, and the relaxed plan\nis returned only if it assigns MORE work than the first — giving\naway constraints for nothing is strictly worse than not giving them\naway. Breaks are never widened, nor are capacities, skills,\nterritories or task caps: only time windows move, and only by the\nstated amounts. Bills as two solves when the second one runs." }, "shipments": { "description": "Pickup+delivery pairs.", "items": { "$ref": "#/$defs/OptimiseShipmentSpec" }, "type": "array" }, "territories": { "description": "Fleet territories: named polygons that bound which vehicle may\nserve which stop, referenced by `vehicles[].territory_ids`. These\nare request data — caller-drawn rounds, validated per call and\nnever stored. Nothing to do with clean-air zones or with the\noffline map packages of the same word.", "items": { "$ref": "#/$defs/TerritorySpec" }, "type": [ "array", "null" ] }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; the travel-time matrix then respects dimensional and\ndangerous-goods restrictions, so the whole plan is truck-legal." }, "vehicles": { "description": "The fleet (at least one vehicle, each with a start and/or end).", "items": { "$ref": "#/$defs/OptimiseVehicleSpec" }, "type": "array" } }, "required": [ "vehicles" ], "type": "object" }, "name": "replan_routes", "outputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "OptimiseSummary": { "description": "Solution summary of the `optimise_routes` tool.", "properties": { "cost": { "description": "Solver cost of the plan (travel seconds under the default model).", "format": "int64", "type": "integer" }, "distance_m": { "description": "Total travel distance in metres, when reported.", "format": "int64", "type": [ "integer", "null" ] }, "duration_s": { "description": "Total travel time in seconds.", "format": "int64", "type": "integer" }, "routes": { "description": "Number of vehicle routes in the plan.", "format": "int64", "type": "integer" }, "service_s": { "description": "Total service time in seconds.", "format": "int64", "type": "integer" }, "unassigned": { "description": "Number of unassigned tasks.", "format": "int64", "type": "integer" }, "waiting_time_s": { "description": "Total waiting time in seconds.", "format": "int64", "type": "integer" } }, "required": [ "cost", "routes", "unassigned", "duration_s", "service_s", "waiting_time_s" ], "type": "object" }, "OptimisedRoute": { "description": "One vehicle's optimised route.", "properties": { "distance_m": { "description": "Total travel distance in metres, when reported.", "format": "int64", "type": [ "integer", "null" ] }, "duration_s": { "description": "Total travel time in seconds.", "format": "int64", "type": "integer" }, "service_s": { "description": "Total on-site service time in seconds.", "format": "int64", "type": "integer" }, "steps": { "description": "Ordered steps: start, tasks in visit order, end.", "items": { "$ref": "#/$defs/OptimisedStep" }, "type": "array" }, "vehicle": { "description": "The vehicle id from the request.", "format": "uint64", "minimum": 0, "type": "integer" }, "waiting_time_s": { "description": "Total waiting time in seconds.", "format": "int64", "type": "integer" } }, "required": [ "vehicle", "duration_s", "service_s", "waiting_time_s", "steps" ], "type": "object" }, "OptimisedStep": { "description": "One step of an optimised vehicle route.", "properties": { "arrival_s": { "description": "Arrival time in seconds (same epoch as the request's windows).", "format": "int64", "type": "integer" }, "duration_s": { "description": "Cumulative travel time when the step begins, in seconds.", "format": "int64", "type": "integer" }, "id": { "description": "The job/shipment-stop id, absent on start/end steps.", "format": "uint64", "minimum": 0, "type": [ "integer", "null" ] }, "load": { "description": "Vehicle load after the step, when reported.", "items": { "format": "int64", "type": "integer" }, "type": [ "array", "null" ] }, "location": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "The step's location resolved back to coordinates." }, "service_s": { "description": "On-site service time in seconds.", "format": "int64", "type": "integer" }, "type": { "description": "Step kind: \"start\", \"job\", \"pickup\", \"delivery\", \"break\" or \"end\".", "type": "string" }, "waiting_time_s": { "description": "Waiting time before the step in seconds.", "format": "int64", "type": "integer" } }, "required": [ "type", "arrival_s", "duration_s", "service_s", "waiting_time_s" ], "type": "object" }, "UnassignedTask": { "description": "One unassigned task of an optimisation solution.", "properties": { "id": { "description": "The task id from the request.", "format": "uint64", "minimum": 0, "type": "integer" }, "location": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "The task's location, when known." }, "type": { "description": "Task kind: \"job\", \"pickup\" or \"delivery\".", "type": "string" } }, "required": [ "id", "type" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "profile": { "description": "The matrix costing profile the plan was computed with (\"auto\",\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\").", "type": "string" }, "relaxation": { "description": "The relaxation report: the caps requested, whether a second solve\nran, whether ITS plan is the one returned, what was widened, and\nwhat is still unassigned. Present only when `relax_if_unassigned`\nwas declared.\n\nAlways read `second_solve` and `relaxed_plan_used` before telling\nanyone the day fits. A plan produced under relaxation has had\npromises moved, and the block is what says so." }, "reloads": { "description": "The multi-trip split: each vehicle's trips, their windows, the\ndepot each returns to, and the stated approximation. Present only\nwhen a vehicle declared `reloads`.\n\nRead the `basis` inside it before quoting arrival times. The trip\nwindows are fixed BEFORE the solve, so a lorry that tips early\ncannot lend the spare time to its next trip: stops can come back\nunassigned that a truly sequential model would have served. The\nplan is feasible, never optimistic — it cannot put a vehicle in two\nplaces at once — but it is not optimal. Re-plan after each tip\nthrough `replan_routes` for the tighter answer." }, "replan": { "description": "What the re-plan locked and why: the prefix held per vehicle, where\neach vehicle re-plans from and how that was decided, stops released\nfrom a vehicle that could no longer serve them, tasks forced\nunassigned, and a note for every id that did not resolve.\n\nRead it. A stop that vanished from the plan is named here rather\nthan left for the dispatcher to notice at four in the afternoon." }, "routes": { "description": "One optimised route per used vehicle.", "items": { "$ref": "#/$defs/OptimisedRoute" }, "type": "array" }, "summary": { "$ref": "#/$defs/OptimiseSummary", "description": "Solution summary." }, "territories": { "description": "How the territories bound the plan: which vehicle was eligible for\nwhat, and any task no eligible vehicle existed for. Present only\nwhen `territories` was declared." }, "unassigned": { "description": "Tasks the solver could not assign to any vehicle.", "items": { "$ref": "#/$defs/UnassignedTask" }, "type": "array" }, "zones": { "description": "Clean-air / low-emission zones touching the problem's own\nlocations, and what the declared vehicle pays in each. Present only\nwhen `emissions` was declared." } }, "required": [ "profile", "summary", "unassigned", "routes", "replan" ], "type": "object" } }, { "description": "Report that the live world disagrees with the map — a closed road, a wrong or missing restriction, a bad speed limit, a missing road, a wrong one-way, or changed access. Use it when you observe the mismatch mid-task. Provide `location` {lat, lon}, a `category` (road_closed, wrong_restriction, wrong_speed_limit, missing_road, wrong_oneway, access_changed, other) and optionally a `description`, the OSM `way_id` and an `evidence_url`. This is a first-party observation: it is QUEUED for human/agent review and NEVER changes routing immediately or edits any map. Returns the queued report_id.", "inputSchema": { "$defs": { "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "MapIssueCategory": { "description": "The kind of map/live-world mismatch an agent is reporting.\n\nA first-party *observation* only: it records what the agent saw on the\nground, never an edit to any map or OSM-derived database.", "oneOf": [ { "const": "road_closed", "description": "A road the map shows as open is closed (roadworks, collapse, event).", "type": "string" }, { "const": "wrong_restriction", "description": "A turn/access/dimension/weight restriction is wrong or missing.", "type": "string" }, { "const": "wrong_speed_limit", "description": "The posted speed limit differs from the map's value.", "type": "string" }, { "const": "missing_road", "description": "A road exists on the ground but is absent from the map.", "type": "string" }, { "const": "wrong_oneway", "description": "A one-way direction is wrong (or the road is not one-way at all).", "type": "string" }, { "const": "access_changed", "description": "Access has changed (e.g. now gated, private, or newly public).", "type": "string" }, { "const": "other", "description": "Anything else that does not fit the categories above.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "category": { "$ref": "#/$defs/MapIssueCategory", "description": "What kind of mismatch this is." }, "description": { "description": "Free-text detail of what was observed on the ground, e.g. \"barrier\nacross the lane, diversion signed via the B4009\". Bounded length.", "type": [ "string", "null" ] }, "evidence_url": { "description": "A URL backing the observation (photo, notice, news item), when one\nexists. Bounded length.", "type": [ "string", "null" ] }, "location": { "$ref": "#/$defs/LatLon", "description": "Where the mismatch was observed (WGS84 decimal degrees)." }, "way_id": { "description": "The OSM way id the observation concerns, when the caller knows it.\nOptional — the report stands on its own as a first-party\nobservation and is never tied to OSM data beyond this hint.", "format": "uint64", "minimum": 0, "type": [ "integer", "null" ] } }, "required": [ "location", "category" ], "type": "object" }, "name": "report_map_issue", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "category": { "description": "The category, echoed back as its snake_case wire tag.", "type": "string" }, "report_id": { "description": "The generated id of the queued report (cite it in follow-ups).", "type": "string" }, "status": { "description": "Always \"queued\": the report awaits human/agent review and changes\nnothing about routing immediately.", "type": "string" } }, "required": [ "report_id", "status", "category" ], "type": "object" } }, { "description": "Turn coordinates into the nearest places: addresses, POIs and localities with distance in metres. The inverse of geocode. Provide `lat` and `lon`; returns up to `limit` (default 5, max 10) results, nearest first, each with name, one-line label, lat/lon, type, address parts and distance_m, plus categories and a `details` object of display tags (opening_hours, website, phone, wikipedia, ...) on POI hits when the index carries them. Every result also carries `bearing_deg` and a spoken `direction`. Pass `heading_deg` (degrees clockwise from true north, 0 = north, 90 = east) and results are described from where the user stands — \"ahead and slightly to your right, about 80 metres\" — with a signed `relative_bearing_deg` (negative left, positive right); without a heading the phrasing falls back to cardinals (\"north-east of you\"), so this works with or without a compass. Add `fov_deg` to keep only what lies within that cone of the heading — it is the FULL width, so 90 keeps what lies within 45 degrees either side of dead ahead; anything dropped is counted in `out_of_view`. Prefer reading `direction` aloud over coordinates.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "fov_deg": { "description": "Field of view: the full width in degrees of a cone centred on\n`heading_deg`, outside which results are dropped — it is the\nFULL width, so 90 keeps only what lies within 45 degrees either\nside of dead ahead. Needs\n`heading_deg` — a cone has to point somewhere. The count of\nresults removed is reported as `out_of_view`.", "format": "double", "type": [ "number", "null" ] }, "heading_deg": { "description": "Which way the user is facing, in degrees **clockwise from true\nnorth** (0 = north, 90 = east, 180 = south, 270 = west). Supply it\nand every result is also described from the user's point of view\n(\"just ahead on your right\"); omit it and results fall back to\ncardinal directions (\"to the north-east\"), so the tool works with\nor without a compass.", "format": "double", "type": [ "number", "null" ] }, "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "limit": { "description": "Maximum number of results (1–10, default 5). The hosted\nfirst-party index answers at most 5 nearest hits per lookup.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, "name": "reverse_geocode", "outputSchema": { "$defs": { "ReverseGeocodeHit": { "description": "One reverse-geocoding result: a place near the queried point. The same\nshape family as [`GeocodeHit`], plus the reverse-only extras\n(`distance_m`, `categories`, `details`) and the egocentric extras\n(`bearing_deg`, `relative_bearing_deg`, `direction`).", "properties": { "bearing_deg": { "description": "Bearing from the queried point to this place, degrees clockwise\nfrom true north. Always present.", "format": "double", "type": [ "number", "null" ] }, "categories": { "description": "POI categories (e.g. [\"cafe\"]), when the index carries them.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "city": { "description": "City or town, when known.", "type": [ "string", "null" ] }, "country": { "description": "Country, when known: a country name, or the ISO 3166-1 alpha-2\ncode (e.g. \"GB\") on first-party hits, which carry only the code.", "type": [ "string", "null" ] }, "details": { "description": "Whitelisted OSM display tags on POI hits (opening_hours, website,\nphone, brand, cuisine, wheelchair, wikipedia, ...), passed through\nverbatim when present." }, "direction": { "description": "The direction phrased for speech — \"ahead and slightly to your\nright, about 80 metres\" with a heading, \"to the north-east, about\n80 metres\" without one. Deliver this verbatim rather than reading\nout coordinates.", "type": [ "string", "null" ] }, "distance_m": { "description": "Straight-line distance from the queried point in metres, when the\nbackend reports one (first-party gateway hits always do).", "format": "double", "type": [ "number", "null" ] }, "label": { "description": "Human-readable one-line label assembled from the address parts.", "type": "string" }, "lat": { "description": "Latitude of the place in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude of the place in decimal degrees.", "format": "double", "type": "number" }, "name": { "description": "Place name, when the source feature has one.", "type": [ "string", "null" ] }, "postcode": { "description": "Postcode, when known.", "type": [ "string", "null" ] }, "relative_bearing_deg": { "description": "Where this place is relative to the way the user is facing:\nnegative to the left, positive to the right, −180 to 180. Present\nonly when the request supplied `heading_deg`.", "format": "double", "type": [ "number", "null" ] }, "type": { "description": "Feature type, e.g. \"address\", \"street\", \"poi\", \"locality\".", "type": [ "string", "null" ] } }, "required": [ "label", "lat", "lon" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "out_of_view": { "description": "How many otherwise-matching places were dropped for falling\noutside `fov_deg`. Non-zero means there are places near the user\nthat are simply not in front of them — say so rather than\nreporting nothing nearby. Always 0 when no field of view was set.", "format": "uint32", "minimum": 0, "type": "integer" }, "results": { "description": "Nearby places, nearest first.", "items": { "$ref": "#/$defs/ReverseGeocodeHit" }, "type": "array" } }, "required": [ "results" ], "type": "object" } }, { "description": "Compute a turn-by-turn route between origin and destination (optionally via waypoints). Costing \"auto\" = car, \"truck\" = lorry, \"bicycle\", \"pedestrian\" = walking, \"motor_scooter\" = moped. Pass `truck` {height_m, width_m, length_m, gross_weight_t, hazmat, tunnel_code} to apply dimensional limits and the ADR dangerous-goods tunnel matrix to the search; `pedestrian` {use_lit 0-1, type \"wheelchair\"|\"blind\", max_hiking_difficulty 1-6} for lit-street walking, accessibility and trail limits; `bicycle` {bicycle_type, use_roads 0-1, use_living_streets 0-1, avoid_bad_surfaces 0-1, use_hills 0-1} for quiet-ride and surface preferences. Returns distance (m), duration (s), maneuvers, polyline6 geometry and the ADR costing that was applied. Any of truck, auto, bicycle, pedestrian or motor_scooter routes may set `rationale: true` (opt-in, costs up to 1 + N extra routing calls) to learn which declared truck constraints or avoidance-side preferences (hills, surfaces, tolls, unlit streets, …) actually changed the route (`rationale.avoided[]`, basis route_divergence — it proves a field was binding, it does not identify the physical restriction or feature, and no live traffic or incident data is ever attributed). ADR honesty: `applied_adr.forbidden_tunnel_categories` describes the LOAD, not the returned route, and `applied_adr.tunnel_enforcement` states the boundary: roads are excluded only where the routing graph records an ADR tunnel category, so an unchanged route is not a clearance. Set `landmarks: true` for turn instructions anchored to recognisable places — each manoeuvre that passes one gains a `landmark_instruction` like \"Turn right just after the Shell garage\" beside the engine's own street-name `instruction`, which is never replaced. Prefer reading it aloud: it is how a passenger gives directions. Nothing is named unless it is recognisable from the road, within 40 m of the junction and not tagged as closed, so many routes return none and a `landmarks.annotated` of 0 with no `note` means this route genuinely passes nothing recognisable. Needs the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), whose place index does the lookup. Through the gateway each manoeuvre also carries the road it starts on: `country`, `subdivision`, `ref` (route numbers only), `road_class`, `speed_limit_unit` (`mph` or `km/h`, from the road's country), `speed_limit_sign` (`vienna` or `mutcd`), `speed_limit_posted` (the limit as the sign shows it, in the road's unit) and `driving_side`; each is absent when the map does not know it, never guessed. Optional `exclude_polygons` for what-if scenarios (\"close this bridge and re-route\"): an array of polygons, each an array of [lon, lat] pairs forming one ring — longitude FIRST — whose intersecting roads are excluded from the search. Applies to the Valhalla engine; unsupported on the GraphHopper engine, where it is ignored. Optional `avoid` and `exclude` name road features to keep off, and the difference between them is not cosmetic. `avoid` (\"tolls\", \"highways\", \"ferries\") is a PREFERENCE: it sets the costing's willingness to zero, and the engine's own reference says that is not guaranteed to avoid the feature — measured, avoiding tolls across the Dartford Crossing returns the same tolled route, because the untolled alternative is fifty kilometres further. `exclude` (\"tolls\", \"highways\", \"ferries\", \"bridges\", \"tunnels\") is a hard exclusion: it can answer NO ROUTE rather than a detour, and it depends on the routing engine's own hard-exclusion setting, which the engine does not report and this server cannot read — so it is requested, never promised. \"tolls\" and \"highways\" under `avoid` exist only on motorised costings; asking for one on a bicycle is refused rather than silently ignored. Whatever is applied comes back in `avoidance`, with the caveats — relay them, because \"avoid tolls\" read as a guarantee is the failure mode here. Each location also takes a kerbside approach: `preferred_side` \"same\" (alias \"curb\") stops on the door's side of the road, resolved against the locale's driving side — the left kerb in the UK, the right in Germany — with \"opposite\" and \"either\" (alias \"unrestricted\") for the rest. It is a snapping preference, not a manoeuvre guarantee, and it needs a coordinate genuinely offset from the road centreline; `street_side_tolerance_m` and `street_side_max_distance_m` bound the window in which it applies, and a pair leaving no window is refused rather than answered with the preference silently inert. Pass `emissions` {vehicle_category, fuel, euro_standard} for UK clean-air-zone assessment: the response's `zones` block then names every zone the route enters and what THIS vehicle pays there, with the publishing authority cited. Without it a zone can only be named, never priced. Needs the gateway, which holds the curated zone dataset; the whole dataset — every scheme, charge, boundary and provenance record — is readable at `GET /v1/zones` on the HTTP API when an agent needs to audit a figure or list zones without routing. Set `scenic: true` (auto costing only) to ask whether there is a prettier way. It is a PEER OFFER, never a substitution: the route in `geometry_polyline6` is byte-for-byte what the same request returns without the flag, and the prettier way, when there is one, arrives beside it in `scenic`, with its own geometry in `scenic.alternative_geometry_polyline6` and what it costs in `scenic.spoken` (\"About seven minutes longer than the quick way\"). EVERY OFFER STATES ITS REASON OR THERE IS NO OFFER: a route that scores well but cannot support a plain sentence is dropped rather than dressed up, so a `scenic.reason` of null means nothing here measured above the floor. That is an ANSWER, not a failure, and it is the one to relay. A REJECTION IS ALSO AN ANSWER: every candidate that lost says why in `scenic.rejections[]` (`same_route`, `no_scenic_gain`, `not_scenic_enough`, `over_time_budget` with the minutes it would have cost, `nothing_to_say`), so \"no prettier way\" is always attributable and raising the budget is an informed choice. It is a re-ranking of routes the engine already proposed, not a scenic search: a beautiful road the engine never offered was never considered, and `scenic.caveats` says so. Like `rationale`, IT COSTS EXTRA METERED COMPUTATIONS, at most two, which is why it is opt-in and never on by default. `available: false` with a `note` means this deployment could not look, which is not the same claim as nothing being there. Needs the gateway, whose basemap archive the scoring is measured against.", "inputSchema": { "$defs": { "AvoidFeature": { "description": "A road feature to avoid as a PREFERENCE, not a ban. \"tolls\" and \"highways\" apply to motorised costings only (auto, truck, bus, motor_scooter, motorcycle); \"ferries\" applies to every costing. Asking for one on a costing whose engine table has no field for it is refused rather than silently ignored.", "enum": [ "tolls", "highways", "ferries" ], "type": "string" }, "BicycleSpec": { "description": "Bicycle options for costing \"bicycle\": the bicycle type plus road,\nsurface and hill preference weights, mapped onto Valhalla\n`costing_options.bicycle`.", "properties": { "avoid_bad_surfaces": { "description": "Avoidance of surfaces unsuited to the bicycle type, 0.0–1.0\n(1.0 = strictly avoid bad surfaces).", "format": "double", "type": [ "number", "null" ] }, "bicycle_type": { "description": "Bicycle type: \"road\", \"hybrid\" (default), \"city\", \"cross\" or\n\"mountain\". Sets default speed and surface tolerance.", "type": [ "string", "null" ] }, "use_hills": { "description": "Willingness to take hills, 0.0–1.0 (0.0 = avoid climbs even at the\ncost of longer routes).", "format": "double", "type": [ "number", "null" ] }, "use_living_streets": { "description": "Preference for living/shared streets, 0.0–1.0.", "format": "double", "type": [ "number", "null" ] }, "use_roads": { "description": "Willingness to ride roads alongside motor traffic, 0.0–1.0\n(0.0 = prefer cycleways and quiet streets — the quiet-ride slider).", "format": "double", "type": [ "number", "null" ] } }, "type": "object" }, "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "EmissionsFuel": { "description": "What a vehicle burns, in clean-air-zone scheme terms.", "oneOf": [ { "const": "petrol", "description": "Petrol, including petrol hybrids (schemes rate a hybrid by its\ncombustion engine's approval).", "type": "string" }, { "const": "diesel", "description": "Diesel, including diesel hybrids.", "type": "string" }, { "const": "electric", "description": "Battery-electric.", "type": "string" }, { "const": "hydrogen", "description": "Hydrogen fuel cell.", "type": "string" }, { "const": "gas", "description": "LPG or CNG; rated as petrol by every scheme in the dataset.", "type": "string" } ] }, "EmissionsSpec": { "description": "A vehicle's emission declaration, for clean-air / low-emission zone\nassessment.", "properties": { "euro_standard": { "description": "Its Euro emission standard, 1–6. Heavy-duty approvals are written\nin Roman numerals (Euro VI); declare Euro VI as `6`. Required for\nany combustion fuel — without it no zone can be resolved, and a\nhalf-declared vehicle is indistinguishable from an undeclared one.\nOptional only for `electric` or `hydrogen`.", "format": "uint8", "maximum": 255, "minimum": 0, "type": [ "integer", "null" ] }, "fuel": { "$ref": "#/$defs/EmissionsFuel", "description": "What it burns." }, "vehicle_category": { "$ref": "#/$defs/EmissionsVehicleCategory", "description": "What kind of vehicle this is, in scheme terms." } }, "required": [ "vehicle_category", "fuel" ], "type": "object" }, "EmissionsVehicleCategory": { "description": "What a vehicle is, in clean-air-zone scheme terms.\n\nDeclaring this turns \"charge depends on vehicle emissions\" into an\nanswer. Without it a zone can only be named, never priced.", "oneOf": [ { "const": "car", "description": "A private car.", "type": "string" }, { "const": "van", "description": "A van or light goods vehicle up to 3.5 tonnes.", "type": "string" }, { "const": "minibus", "description": "A minibus (typically 8+ passenger seats, up to 5 tonnes).", "type": "string" }, { "const": "hgv", "description": "A heavy goods vehicle over 3.5 tonnes.", "type": "string" }, { "const": "bus", "description": "A bus over 5 tonnes.", "type": "string" }, { "const": "coach", "description": "A coach over 5 tonnes.", "type": "string" }, { "const": "taxi", "description": "A licensed hackney carriage.", "type": "string" }, { "const": "phv", "description": "A private hire vehicle.", "type": "string" }, { "const": "motorcycle", "description": "A motorcycle, moped or tricycle.", "type": "string" }, { "const": "motorhome", "description": "A motor caravan or campervan.", "type": "string" } ] }, "ExcludeFeature": { "description": "A road feature to exclude outright. A hard exclusion can leave a request with no path at all — that is the honest answer, not a failure — and it depends on the routing engine's own hard-exclusion setting, which nothing here can read.", "enum": [ "tolls", "highways", "ferries", "bridges", "tunnels" ], "type": "string" }, "PedestrianSpec": { "description": "Pedestrian options for costing \"pedestrian\": lit-street preference,\naccessibility type and hiking difficulty, mapped onto Valhalla\n`costing_options.pedestrian`. All preferences, never guarantees — the\nrouter prefers matching ways where the map data supports it.", "properties": { "max_hiking_difficulty": { "description": "Maximum hiking-trail difficulty the route may use, as OSM\n`sac_scale` 1–6 (1 = well-cleared, mostly flat trails; 6 =\ndemanding alpine terrain). Default 1.", "format": "uint8", "maximum": 255, "minimum": 0, "type": [ "integer", "null" ] }, "type": { "description": "Pedestrian type: \"wheelchair\" (avoids steps, kerbs and steep\ngrades where mapped) or \"blind\" (richer guidance detail). Omit for\nthe default on-foot profile.", "type": [ "string", "null" ] }, "use_lit": { "description": "Preference for lit streets, 0.0–1.0 (1.0 = prefer lit paths as\nstrongly as possible — the \"walk me home on lit streets\" option).\nUnlit ways are still used when no lit alternative exists.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" }, "PreferredSideKind": { "description": "Side-of-street preference for arriving at or departing from a location.\n\nCarries both vocabularies: MapMap's own `same`/`opposite`/`either` and\nthe OSRM/Mapbox `approaches` words `curb`/`unrestricted`, which are\naliases for `same` and `either`. Both spell the same request, on this\nsurface and on the HTTP API.", "oneOf": [ { "const": "same", "description": "The side the location itself projects to — the kerb, resolved\nagainst the locale's driving side (the left kerb in the UK, the\nright in Germany).", "type": "string" }, { "const": "opposite", "description": "The far side of the road from the location.", "type": "string" }, { "const": "either", "description": "No side preference.", "type": "string" }, { "const": "curb", "description": "Alias for `same`, from the OSRM/Mapbox `approaches` vocabulary.", "type": "string" }, { "const": "unrestricted", "description": "Alias for `either`, from the OSRM/Mapbox `approaches` vocabulary.", "type": "string" } ] }, "RouteLocation": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "One location of a `route` request: a coordinate, plus the optional\nkerbside approach for arriving at it.\n\nA bare `{lat, lon}` is still a complete location — every kerbside field\nis optional and omitting all of them is exactly the request that was\nmade before they existed.", "properties": { "preferred_side": { "anyOf": [ { "$ref": "#/$defs/PreferredSideKind" }, { "type": "null" } ], "description": "Which side of the street to arrive on (or depart from). `same` (or\n`curb`) puts the vehicle on the door's side of the road, resolved\nagainst the locale's driving side. Two honest limits: this is a\n**snapping preference**, not a manoeuvre guarantee — the engine\nprefers an edge on that side, it does not promise the driver never\ncrosses — and it needs a coordinate genuinely offset from the road\ncentreline, because a point on the centreline has no side." }, "street_side_max_distance_m": { "description": "Metres: further than this from the road centreline, the side of\nstreet is treated as `none` and `preferred_side` does nothing.\nEngine default 1000 m. Together with `street_side_tolerance_m` this\nis a WINDOW: a pair that leaves no window (tolerance at or above\nmax distance) is refused here rather than answered with a route on\nwhich the kerbside preference was silently inert.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "street_side_tolerance_m": { "description": "Metres: nearer than this to the road centreline, the side of street\nis treated as `none` and `preferred_side` does nothing. Engine\ndefault 5 m.", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] } }, "type": "object" }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "avoid": { "description": "Road features to avoid as a PREFERENCE. Rendered as the costing's\nwillingness factor set to zero — a thumb on the scale, not a ban.\nThe engine's own reference is explicit that a value of zero is not\nguaranteed to avoid the feature entirely, and it does not: asking\nto avoid tolls across the Dartford Crossing returns the same tolled\nroute, because the untolled alternative is fifty kilometres\nfurther. Use `exclude` when you mean a ban.", "items": { "$ref": "#/$defs/AvoidFeature" }, "type": [ "array", "null" ] }, "bicycle": { "anyOf": [ { "$ref": "#/$defs/BicycleSpec" }, { "type": "null" } ], "description": "Bicycle options (bicycle type, road/surface/hill preferences).\nRequires costing \"bicycle\"." }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model: \"auto\" (default), \"truck\", \"bicycle\", \"pedestrian\"\nor \"motor_scooter\"." }, "destination": { "$ref": "#/$defs/RouteLocation", "description": "Route destination." }, "emissions": { "anyOf": [ { "$ref": "#/$defs/EmissionsSpec" }, { "type": "null" } ], "description": "The vehicle's emission declaration, for UK clean-air / low-emission\nzone assessment. Given, the response's `zones` block names every\nzone the route enters and what THIS vehicle pays there, with the\npublishing authority cited. Without it a zone can only be named,\nnever priced. Needs the MapMap gateway, which holds the curated\nzone dataset; the full dataset is readable at `GET /v1/zones`." }, "exclude": { "description": "Road features to exclude outright, rendered as the costing's hard\nexclusion flag. Two things follow from that and both matter: a hard\nexclusion can return NO ROUTE rather than a detour (excluding\ntunnels on a Rotherhithe crossing has no answer), and the flags\ndepend on the routing engine's `allow_hard_exclusions` setting,\nwhich the engine does not expose and nothing here can read — so\nthis is never promised, only requested.", "items": { "$ref": "#/$defs/ExcludeFeature" }, "type": [ "array", "null" ] }, "exclude_polygons": { "description": "Areas to avoid — scenario analysis (\"close this bridge and\nre-route\"): an array of polygons, each an array of `[lon, lat]`\npairs forming one exterior ring (GeoJSON-style, longitude FIRST).\nRoads intersecting any ring are excluded from the search. Applies\nto the Valhalla engine; unsupported on the GraphHopper engine,\nwhere it is ignored.", "items": { "items": { "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" }, "type": "array" }, "type": [ "array", "null" ] }, "landmarks": { "description": "Name landmarks in the turn instructions (default false): each\nmanoeuvre that passes a recognisable place — a petrol station, a\nsupermarket, a household-name chain — gains a\n`landmark_instruction` like \"Turn right just after the Shell\ngarage\" beside the engine's own street-name instruction, which is\nnever replaced. Nothing is named unless it is recognisable from the\nroad, within 40 m of the junction and not tagged as closed, so many\nroutes come back with none: a wrong landmark is worse than no\nlandmark. Needs the MapMap gateway, whose place index does the\nlookup.", "type": [ "boolean", "null" ] }, "origin": { "$ref": "#/$defs/RouteLocation", "description": "Route origin." }, "pedestrian": { "anyOf": [ { "$ref": "#/$defs/PedestrianSpec" }, { "type": "null" } ], "description": "Pedestrian options (lit-street preference, wheelchair/blind type,\nhiking difficulty). Requires costing \"pedestrian\"." }, "rationale": { "description": "Explain the route (default false): re-routes with each declared\ntruck constraint (truck costing) or avoidance-side routing\npreference (auto, bicycle, pedestrian, motor_scooter) relaxed and\nreports the ones that actually changed the route as\n`rationale.avoided[]`. Opt-in — it costs up to 1 + N extra routing\ncalls, one per declared field plus one combined probe, and it is\nbilled for the ones it actually makes: at most 8 in total,\ntypically fewer, and 1 when there is nothing to probe. Against the\nhosted gateway each probe is its own metered route call, which is\nexactly what `POST /route` with `rationale: true` charges for its\nown fan-out, so the two surfaces price the same explanation the\nsame way.", "type": [ "boolean", "null" ] }, "scenic": { "description": "Ask whether there is a prettier way (default false, `auto`\ncosting only). The route in `geometry_polyline6` is NEVER swapped:\nwhat comes back is a peer offer beside it, in `scenic`, carrying\nthe plain sentence that justifies it. No sentence, no offer: a\nroute that scores well but cannot support one is dropped rather\nthan dressed up, and `scenic.reason` is then null, which is an\nanswer. Every candidate that lost says why in\n`scenic.rejections[]` (`same_route`, `no_scenic_gain`,\n`not_scenic_enough`, `over_time_budget`, `nothing_to_say`), so\n\"no prettier way\" is always attributable. Opt-in because it\ncosts: at most two extra metered route computations, the same\nbilling shape as `rationale`. Needs the MapMap gateway, whose\nbasemap archive the scoring is measured against.", "type": [ "boolean", "null" ] }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; when present, ADR dangerous-goods costing options are\nmerged into the request." }, "waypoints": { "description": "Optional intermediate stops, visited in order between origin and\ndestination.", "items": { "$ref": "#/$defs/RouteLocation" }, "type": [ "array", "null" ] } }, "required": [ "origin", "destination" ], "type": "object" }, "name": "route", "outputSchema": { "$defs": { "AppliedAdr": { "description": "The ADR costing that was merged into a truck route request.\n\nEvery field here describes the **request**: the declared load and the\ncosting options built from it. Nothing here is an assertion about the\nreturned geometry — see [`TunnelEnforcement`].", "properties": { "costing_options": { "description": "The exact Valhalla `costing_options` JSON merged into the request." }, "forbidden_tunnel_categories": { "description": "ADR tunnel categories **the vehicle** is forbidden from under the\nworst-case reading of ADR 8.6.4 (conditional clauses assumed to\napply). Empty when unrestricted. This is not a claim that the\nreturned route contains no tunnel of these categories — read\n[`AppliedAdr::tunnel_enforcement`] for what was actually applied.", "items": { "type": "string" }, "type": "array" }, "hazmat": { "description": "Whether the profile declared dangerous goods.", "type": "boolean" }, "tunnel_code": { "description": "Canonical ADR tunnel restriction code applied (\"B/D\", \"(—)\", …),\nor null when no code was declared.", "type": [ "string", "null" ] }, "tunnel_enforcement": { "$ref": "#/$defs/TunnelEnforcement", "description": "What the router did with `forbidden_tunnel_categories`, and the\nboundary of the resulting guarantee." } }, "required": [ "hazmat", "forbidden_tunnel_categories", "tunnel_enforcement", "costing_options" ], "type": "object" }, "AppliedAvoidance": { "description": "What the avoidance lists actually did, reported back so a 200 is never\nreadable as a certificate.", "properties": { "avoid": { "description": "The `avoid` values that were applied, echoed back.", "items": { "type": "string" }, "type": "array" }, "caveats": { "description": "The caveats that apply to this request, in words fit to repeat to a\nuser. Always present when anything was applied.", "items": { "type": "string" }, "type": "array" }, "costing_option_fields": { "description": "The engine costing-option fields these words became, e.g.\n`{\"use_tolls\": 0.0, \"exclude_ferries\": true}`. This is the whole of\nwhat was sent — there is no hidden second mechanism." }, "exclude": { "description": "The `exclude` values that were applied, echoed back.", "items": { "type": "string" }, "type": "array" } }, "required": [ "costing_option_fields", "caveats" ], "type": "object" }, "AvoidedConstraint": { "description": "One constraint or preference the route provably changed for, derived\nby route divergence (see [`RouteRationale`]).", "properties": { "baseline": { "description": "What the probe compared against: \"relaxed_to_non_binding\" (truck\nconstraints set to explicitly non-binding values),\n\"engine_default\" (preference removed so the engine default\napplies) or \"cap_lifted\" (a hard cap raised to its maximum).", "type": "string" }, "basis": { "description": "Always \"route_divergence\": the entry was derived by re-routing\nwithout the field and diffing geometry, not from sign or\nmap-feature data.", "type": "string" }, "constraint": { "description": "The costing-options field that was relaxed (\"height\", \"use_hills\",\n…, or \"combined\").", "type": "string" }, "distance_saved_m": { "description": "Distance the constraint costs, metres.", "format": "double", "type": "number" }, "diverged_length_m": { "description": "Length of the diverging span along the constrained route, metres.", "format": "double", "type": "number" }, "fields": { "description": "For kind \"combined\": the constraint fields that only bind together.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "kind": { "description": "Reason kind. Truck: \"max_height\", \"max_width\", \"max_length\",\n\"max_weight\", \"hazmat\", \"adr_tunnel\". Preferences: \"hill_avoided\",\n\"surface_avoided\", \"road_avoided\", \"highway_avoided\",\n\"toll_avoided\", \"ferry_avoided\", \"living_street_avoided\",\n\"primary_road_avoided\", \"unlit_street_avoided\",\n\"hiking_difficulty_limited\", \"access_profile\". Any costing:\n\"combined\". There is deliberately no \"incident_avoided\" or\n\"traffic_delay_avoided\": the engine consumes no live incident or\ntraffic-speed input, so no divergence can be attributed to them.", "type": "string" }, "location": { "$ref": "#/$defs/LatLon", "description": "Where the constrained and unconstrained routes part ways." }, "reason": { "description": "Human-readable explanation, honest about the method.", "type": "string" }, "rejoins_at": { "$ref": "#/$defs/LatLon", "description": "Where they rejoin." }, "time_saved_s": { "description": "Travel time the constraint costs (unconstrained is this much\nfaster), seconds. Zero when the alternative is no faster.", "format": "double", "type": "number" }, "unit": { "description": "Unit of `value` (\"m\", \"t\") when dimensional.", "type": [ "string", "null" ] }, "value": { "description": "The declared value for the field (null for \"combined\"). NOTE: for\ntruck this is the vehicle's dimension, not the infrastructure\nlimit — the physical restriction is not identified." } }, "required": [ "kind", "constraint", "value", "basis", "baseline", "location", "rejoins_at", "diverged_length_m", "time_saved_s", "distance_saved_m", "reason" ], "type": "object" }, "LandmarkSummary": { "description": "The gateway's `landmarks` summary block.", "properties": { "annotated": { "description": "How many manoeuvres gained a `landmark_instruction`.", "format": "uint32", "minimum": 0, "type": "integer" }, "note": { "description": "Why the count is what it is, when there is something to say: the\nper-route lookup cap was reached, or the deployment has no place\nindex at all. Absent means the number is simply the number, so a\nzero with no note means this route genuinely passes nothing\nrecognisable rather than that landmarks could not be looked up.", "type": [ "string", "null" ] } }, "required": [ "annotated" ], "type": "object" }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "ManeuverOut": { "description": "One turn-by-turn instruction of a computed route.", "properties": { "country": { "description": "ISO 3166-1 alpha-2 country of the road the manoeuvre starts on, e.g.\n`\"GB\"`. This and the road fields below come from the MapMap gateway's\nroad walk: present when the route was computed through the gateway\n(GATEWAY_URL + GATEWAY_API_KEY) and the map knows the road, absent\notherwise. Never inferred.", "type": [ "string", "null" ] }, "distance_m": { "description": "Length of the maneuver in metres.", "format": "double", "type": "number" }, "driving_side": { "description": "The side of the road traffic keeps to there, `\"left\"` or `\"right\"`;\npresent only when the road's country is known.", "type": [ "string", "null" ] }, "duration_s": { "description": "Estimated duration of the maneuver in seconds.", "format": "double", "type": "number" }, "instruction": { "description": "Written instruction, e.g. \"Turn right onto Main Street\".", "type": "string" }, "landmark_instruction": { "description": "The same manoeuvre anchored to a recognisable place, e.g. \"Turn\nright just after the Shell garage\" — present only when the request\nset `landmarks: true` and a place near the junction cleared the\nsalience bar. Prefer reading this aloud when it is there: it is how\na passenger would give the direction. `instruction` is always the\nengine's own and is never replaced.", "type": [ "string", "null" ] }, "ref": { "description": "Route numbers of that road (`\"; \"` separated, e.g. `\"A27\"` or\n`\"I 5; US 101\"`); absent when it carries none, never read from a\nstreet name.", "type": [ "string", "null" ] }, "road_class": { "description": "Engine road class of that road (`motorway`, `trunk`, `primary`,\n`secondary`, `tertiary`, `unclassified`, `residential`,\n`service_other`).", "type": [ "string", "null" ] }, "speed_limit_posted": { "description": "The posted limit as the sign shows it, in the road's own unit:\n`{\"type\":\"known\",\"value\":30,\"unit\":\"mph\"}` (plus `\"national\":true`\nfor a national limit), `{\"type\":\"unlimited\"}` or `{\"type\":\"unknown\"}`.\nRead from the road the manoeuvre mostly travels along." }, "speed_limit_sign": { "description": "The speed-limit sign family there: `\"vienna\"` (roundel) or\n`\"mutcd\"` (US and Canadian rectangle).", "type": [ "string", "null" ] }, "speed_limit_unit": { "description": "The unit speed limits are posted in on that road, `\"mph\"` or\n`\"km/h\"`, from its country (never from the request's units).", "type": [ "string", "null" ] }, "subdivision": { "description": "ISO 3166-2 subdivision of that road where the map carries one, e.g.\n`\"GB-ENG\"` or `\"US-CA\"`.", "type": [ "string", "null" ] } }, "required": [ "instruction", "distance_m", "duration_s" ], "type": "object" }, "RouteRationale": { "description": "Why the route goes the way it does — the `route` tool's opt-in\nrationale block, computed for truck, auto, bicycle, pedestrian and\nmotor_scooter costings. **Method honesty:** the routing engine exposes\nno exclusion set, so entries are derived by re-routing with each\ndeclared constraint or preference relaxed\n(`basis: \"route_divergence\"`); this proves a field was binding and\nwhere, but never names the physical restriction or feature (the\nspecific signed bridge, hill or unlit street). No live traffic or\nincident data enters the comparison and no delay is ever estimated\nfrom one — the engine has no live speed field.", "properties": { "avoided": { "description": "The declared constraints or preferences that changed the route,\nwith locations and costs. Empty when nothing was binding.", "items": { "$ref": "#/$defs/AvoidedConstraint" }, "type": "array" }, "caveats": { "description": "The method's limits, spelled out for relaying to users.", "type": "string" }, "method": { "description": "Always \"route_divergence\".", "type": "string" }, "note": { "description": "Why the list is empty or incomplete, when it is.", "type": [ "string", "null" ] } }, "required": [ "method", "avoided", "caveats" ], "type": "object" }, "ScenicOffer": { "description": "The scenic assessment beside a route: a peer offer, never a\nsubstitution.\n\nThe fields here are the ones a client must actually read before\nsaying anything. `block` keeps the gateway's whole answer beside them\nso a figure can always be audited back to the source that produced\nit, rather than to this mapping.", "properties": { "alternative_geometry_polyline6": { "description": "The scenic route's own geometry, polyline6, when there is one to\noffer. `geometry_polyline6` on the route itself is byte-for-byte\nwhat the same request returns without the flag.", "type": [ "string", "null" ] }, "available": { "description": "Whether scenery could be assessed at all on this deployment. When\nfalse, `note` says why and nothing below was measured: that is\n\"we could not look\", never \"there is nothing there\".", "type": "boolean" }, "block": { "description": "The gateway's whole `scenic` block, verbatim: candidates, feature\nshares, tile coverage, metering and budget." }, "caveats": { "description": "The standing limits of the method, for relaying to users.", "type": "string" }, "chosen": { "description": "Which route the assessment would offer: `\"fastest\"` (the quick way\nis already the pretty one, or nothing beat it) or `\"scenic\"`.", "type": "string" }, "extra_time_s": { "description": "How much longer the offered route takes than the fastest one,\nseconds, and the sentence for it. Absent when nothing was offered.", "format": "double", "type": [ "number", "null" ] }, "note": { "description": "Why no assessment ran (only when `available` is false).", "type": [ "string", "null" ] }, "reason": { "description": "The plain, speakable sentence behind the offer. **Null is an\nanswer**: nothing on this corridor measured above the floor, so\nno claim is made rather than one being invented.", "type": [ "string", "null" ] }, "rejections": { "description": "Why each candidate that lost lost. Empty when none did. Relay it:\nit is what makes \"no prettier way\" an attributable answer instead\nof a shrug.", "items": { "$ref": "#/$defs/ScenicRejection" }, "type": "array" }, "spoken": { "description": "The trade phrased for speech, e.g. \"About seven minutes longer\nthan the quick way.\" Prefer reading this aloud over the seconds.", "type": [ "string", "null" ] } }, "required": [ "available", "chosen", "caveats", "block" ], "type": "object" }, "ScenicRejection": { "description": "Why one candidate route was not offered.", "properties": { "code": { "description": "The rejection code: `same_route`, `no_scenic_gain`,\n`not_scenic_enough`, `over_time_budget` or `nothing_to_say`.", "type": "string" }, "detail": { "description": "The code in words, including the minutes an `over_time_budget`\ncandidate would have cost, so raising the budget is an informed\nchoice rather than a guess.", "type": "string" }, "source": { "description": "Where the candidate came from: `\"fastest\"`, `\"engine_alternate\"`\nor `\"no_motorway\"`.", "type": "string" } }, "required": [ "source", "code", "detail" ], "type": "object" }, "TunnelEnforcement": { "description": "How the declared ADR tunnel code reaches the routing engine, and what a\nreturned route therefore does — and does not — prove.\n\nThis block exists because [`AppliedAdr::forbidden_tunnel_categories`] is\na statement about the **load** (ADR 8.6.4 applied to the declared code),\nand on its own it reads like a statement about the **route**. It is not\none. The engine excludes a road only where the routing graph records an\nADR tunnel category for it, and that category is derived from OSM\n`hazmat:*` tagging. Coverage is therefore uneven: a corridor with no\nsuch tagging is not excluded and looks, in the response, exactly like a\ncorridor that was checked and cleared.\n\nThe field is emitted on every `applied_adr`, so a caller can never\nreceive the categories without the boundary that qualifies them.", "properties": { "basis": { "description": "How the code is applied. `\"graph_adr_tunnel_category\"`: merged into\n`costing_options.truck.adr_tunnel_code` and matched, during the\nsearch, against each road's ADR tunnel category as recorded in the\nrouting graph.", "type": "string" }, "caveat": { "description": "What an unchanged route does and does not prove, in one sentence.", "type": "string" }, "route_certified": { "description": "Whether the returned route has been checked against a tunnel\ninventory *after* it was computed. Always `false`: exclusion happens\nduring the search, and only for roads the graph has a category for.\nA 200 is not a compliance certificate, and must not be relied on as\none.", "type": "boolean" } }, "required": [ "basis", "route_certified", "caveat" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "applied_adr": { "anyOf": [ { "$ref": "#/$defs/AppliedAdr" }, { "type": "null" } ], "description": "The ADR costing merged into the request, or null when no truck\nprofile was given." }, "avoidance": { "anyOf": [ { "$ref": "#/$defs/AppliedAvoidance" }, { "type": "null" } ], "description": "What `avoid`/`exclude` became, and the caveats that go with it\n(only when either list carried something)." }, "distance_m": { "description": "Total route distance in metres.", "format": "double", "type": "number" }, "duration_s": { "description": "Total estimated travel time in seconds.", "format": "double", "type": "number" }, "geometry_polyline6": { "description": "Full route geometry as a Google encoded polyline with six digits of\ndecimal precision (polyline6).", "type": "string" }, "landmarks": { "anyOf": [ { "$ref": "#/$defs/LandmarkSummary" }, { "type": "null" } ], "description": "The landmark-annotation summary (only when `landmarks: true` was\nrequested): how many manoeuvres gained a `landmark_instruction`,\nand a `note` when the per-route cap was hit or the deployment has\nno place index. A zero with no note means this route genuinely\npasses nothing recognisable." }, "maneuvers": { "description": "Ordered turn-by-turn maneuvers across all legs.", "items": { "$ref": "#/$defs/ManeuverOut" }, "type": "array" }, "rationale": { "anyOf": [ { "$ref": "#/$defs/RouteRationale" }, { "type": "null" } ], "description": "Why the route goes this way (only when `rationale: true` was\nrequested; computed for truck, auto, bicycle, pedestrian and\nmotor_scooter costings)." }, "scenic": { "anyOf": [ { "$ref": "#/$defs/ScenicOffer" }, { "type": "null" } ], "description": "The scenic peer offer (only when `scenic: true` was requested):\nwhether there is a prettier way, the sentence that justifies it,\nwhat the detour costs, and why every candidate that lost lost.\nThe route above is never swapped for it." }, "summary": { "description": "One-line human-readable summary of the route.", "type": "string" }, "zones": { "description": "Clean-air / low-emission zones this route enters and what the\ndeclared vehicle pays in each, with the publishing authority cited\n(only when `emissions` was declared). Passed through verbatim from\nthe gateway's curated dataset — the same figures `GET /v1/zones`\npublishes, so a charge can always be audited back to its source." } }, "required": [ "distance_m", "duration_s", "summary", "maneuvers", "geometry_polyline6" ], "type": "object" } }, { "description": "What a journey PASSES, in sentences ready to be read aloud, each with its position along the route. The named rivers and canals it crosses, the road it runs on and for how far, the settlements it goes through, the protected landscapes it enters and how high the road climbs: the things a passenger who knew the area would say. NONE OF THIS IS IN A ROUTE: a route is a list of movements and a river crossing is not a movement, so do not try to read it out of `route`'s answer. Give `geometry_polyline6` straight from the `route` tool, plus optional `units` (\"miles\" default, or \"kilometers\"), `min_gap_m` and `max_observations`. Each observation carries `at_m` (WHERE it belongs (delivered anywhere else it is trivia, not an observation), a `kind`, a `say` sentence to relay verbatim, and a `basis` naming the map layer and tag the claim rests on. THE SILENCE BUDGET IS THE DESIGN: `min_gap_m` is a floor, not a target, and when a route yields more than `max_observations` the gap WIDENS on its own so the survivors stay spread over the whole journey rather than clustering where the map happened to be richest. Inside a window the rarer kind wins. So a short list on a long route is the budget working, not thin data. READ `coverage` BEFORE REPORTING AN EMPTY LIST: an empty `observations` with `tiles_read` of 0 means NO DATA WAS READ, which is not the same claim as quiet countryside, and `uncovered_m` says how much of the route could not be seen at all. Two things it will never say, deliberately: it never names a hill (a peak two kilometres away behind a ridge is named confidently and seen by nobody), and it never reads a junction number (an observation mistakable for an instruction is not safe to speak beside real guidance). A settlement is a labelled POINT, not a boundary, and `offset_m` publishes the distance behind the word \"through\". It is a query and it triggers nothing: no position is held and nothing is pushed. COSTS 10 UNITS a call, flat, whatever is asked for. Requires the MapMap gateway (GATEWAY_URL + GATEWAY_API_KEY), whose basemap archive and elevation model answer it; a deployment with no archive answers 501 and says so.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "geometry_polyline6": { "description": "The route shape, precision-6 encoded: `geometry_polyline6`\nstraight out of the `route` tool's answer. Nothing else is\naccepted: there is no origin/destination form, because this tool\nannotates a route somebody already chose rather than computing\none.", "type": "string" }, "max_observations": { "description": "Ceiling on how many observations come back, 1 to 200 (default\n40).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "min_gap_m": { "description": "Least distance along the route between two observations, metres\n(default 2000). A FLOOR, not a target: when the route holds more\nthan `max_observations`, the gap widens past this on its own so\nthe survivors stay spread over the whole journey.", "format": "double", "type": [ "number", "null" ] }, "units": { "description": "Units for the spoken distances: `miles` (default) or\n`kilometers`. `kilometres` and `km` are accepted too.", "type": [ "string", "null" ] } }, "required": [ "geometry_polyline6" ], "type": "object" }, "name": "route_observations", "outputSchema": { "$defs": { "RouteObservation": { "description": "One thing worth saying about one point on a route.", "properties": { "at_m": { "description": "Distance along the route where this applies, metres. The whole\npoint of the answer: said anywhere else it is trivia, not an\nobservation.", "format": "double", "type": "number" }, "basis": { "description": "Which map layer and tag the claim rests on, so anybody who doubts\none can go and look at the same feature.", "type": "string" }, "elevation_m": { "description": "For a climb, the height at the top, metres above sea level.", "format": "double", "type": [ "number", "null" ] }, "kind": { "description": "`crossing`, `landscape`, `settlement`, `watercourse`, `road` or\n`climb`. Each claims a different thing and rests on different\nevidence.", "type": "string" }, "offset_m": { "description": "For a settlement, how far off the route the mapped centre lies,\nmetres. A settlement is a labelled POINT, not a boundary, so this\nis the number behind the word \"through\" and nobody has to take it\non trust.", "format": "double", "type": [ "number", "null" ] }, "run_m": { "description": "For a run-length observation (a road or a landscape), how far the\nrun lasted, metres.", "format": "double", "type": [ "number", "null" ] }, "say": { "description": "The sentence to say. Prefer reading this aloud verbatim: it is\nalready shaped for speech and already checked for length.", "type": "string" }, "subject": { "description": "The subject's name exactly as the map records it, before any\nwording.", "type": "string" } }, "required": [ "at_m", "kind", "subject", "say", "basis" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "attribution": { "description": "The licence notice for this answer. The obligation attaches to the\nproduct, not to each spoken sentence, so a visible or linked\ncredit in the app satisfies it.", "type": "string" }, "caveat": { "description": "The standing limits of the method, for relaying to users.", "type": "string" }, "coverage": { "description": "What the archive and the elevation model could and could not say:\n`tiles_read`, `tiles_missing`, `uncovered_m`, and the per-kind\ncensuses. **Read it before reporting an empty list.** `tiles_read`\nof 0 means nothing was looked at, which is not the same claim as\nquiet countryside." }, "observations": { "description": "The observations, in route order, already thinned to the silence\nbudget.", "items": { "$ref": "#/$defs/RouteObservation" }, "type": "array" }, "route_length_m": { "description": "The route's own length, metres, as walked.", "format": "double", "type": "number" } }, "required": [ "route_length_m", "observations", "coverage", "attribution", "caveat" ], "type": "object" } }, { "description": "Find places (POIs) along a route with the REAL extra travel time of stopping at each — never a straight-line guess. Provide `origin` + `destination` (a route is computed) or an existing route's `geometry_polyline6`, plus a free-text `query` (\"coffee\", \"EV charger\", \"truck stop\") and `max_detour_minutes` (default 10). For a category intent (\"fuel\", \"EV charger\", \"coffee\") pass `category` instead of relying on words alone: it takes the same vocabulary as `nearby_places` (lowercased OSM tag values such as \"fuel\", \"cafe\", \"charging_station\", \"parking\", \"pharmacy\"), and common colloquial phrases are normalised server-side (\"petrol station\" and \"gas station\" to fuel, \"coffee\" to cafe, \"EV charger\" to charging_station). `query` alone also promotes a pure category phrase to the same browse, so \"fuel\" finds fuel stations rather than places whose NAME starts \"Ful\"; anything else stays free-text name matching. When a browse ran, the response echoes the tokens used in `matched_categories`. Candidates near the route corridor are priced through the routing engine with your costing: detour = (origin→place) + (place→destination) − (origin→destination). Costing \"auto\", \"truck\" (with a `truck` profile the detours respect dimensional/ADR restrictions), \"bicycle\", \"pedestrian\" or \"motor_scooter\". Returns results sorted by detour with detour_minutes, detour_km, along_route_position (0-1) and off_route_m; at most 25 candidates are priced per call (candidate_cap).", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "LatLon": { "anyOf": [ { "properties": { "lat": { "description": "Latitude in decimal degrees (−90 to 90).", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees (−180 to 180).", "format": "double", "type": "number" } }, "required": [ "lat", "lon" ], "type": "object" }, { "description": "GeoJSON position [lon, lat]: longitude FIRST.", "items": { "format": "double", "type": "number" }, "maxItems": 2, "minItems": 2, "type": "array" } ], "description": "A WGS84 coordinate in decimal degrees: a {lat, lon} object (preferred), or a GeoJSON [lon, lat] array with LONGITUDE FIRST, the same order as every polygon field on this server." }, "TruckSpec": { "description": "Truck profile for routing: physical dimensions plus the ADR\ndangerous-goods declaration. Omitted dimensions default to the EU\nmaximum authorised dimensions of Council Directive 96/53/EC (4.0 m\nheight, 2.55 m width, 16.5 m length, 40 t gross weight).", "properties": { "gross_weight_t": { "description": "Gross combination weight in metric tonnes.", "format": "double", "type": [ "number", "null" ] }, "hazmat": { "default": false, "description": "Whether the vehicle carries dangerous goods (ADR). Defaults to\nfalse.", "type": "boolean" }, "height_m": { "description": "Vehicle height in metres.", "format": "double", "type": [ "number", "null" ] }, "length_m": { "description": "Vehicle length in metres.", "format": "double", "type": [ "number", "null" ] }, "tunnel_code": { "description": "ADR 8.6.4 tunnel restriction code of the load, e.g. \"B\", \"C5000D\",\n\"B/D\", or \"(—)\"/\"none\" for explicitly unrestricted. Leave unset if\nunknown: a hazmat load without a code is conservatively treated as\ncode B (allowed only through category-A tunnels).", "type": [ "string", "null" ] }, "width_m": { "description": "Vehicle width in metres.", "format": "double", "type": [ "number", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "category": { "description": "Explicit place category (\"fuel\", \"cafe\", \"charging_station\" — same\nvocabulary as nearby_places). Colloquial phrases are normalised\nserver-side; prefer this over query for category intents.", "type": [ "string", "null" ] }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing model for the route and detour matrix: \"auto\" (default),\n\"truck\", \"bicycle\", \"pedestrian\" or \"motor_scooter\"." }, "destination": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route destination." }, "geometry_polyline6": { "description": "An existing route geometry as an encoded polyline6 (the `route`\ntool's `geometry_polyline6`). Provide either this or `origin` +\n`destination`, not both.", "type": [ "string", "null" ] }, "max_detour_minutes": { "description": "Largest acceptable detour in minutes (default 10, at most 120).", "format": "double", "type": [ "number", "null" ] }, "max_results": { "description": "Maximum results (default 5, at most 25).", "format": "uint32", "minimum": 0, "type": [ "integer", "null" ] }, "origin": { "anyOf": [ { "$ref": "#/$defs/LatLon" }, { "type": "null" } ], "description": "Route origin (with `destination`, when no geometry is given)." }, "query": { "description": "Free-text POI query, e.g. \"coffee\", \"EV charger\", \"truck stop\".", "type": "string" }, "truck": { "anyOf": [ { "$ref": "#/$defs/TruckSpec" }, { "type": "null" } ], "description": "Truck profile (dimensions + ADR declaration). Requires costing\n\"truck\"; the detours then respect dimensional/ADR restrictions." } }, "required": [ "query" ], "type": "object" }, "name": "search_along_route", "outputSchema": { "$defs": { "AlongRouteHit": { "description": "One place found along the route, with its honest detour cost.", "properties": { "along_route_position": { "description": "Where along the route the place sits, 0.0 (origin) to 1.0\n(destination), by distance along the geometry.", "format": "double", "type": "number" }, "detour_km": { "description": "Extra travel distance in kilometres, when the engine reported\ndistances.", "format": "double", "type": [ "number", "null" ] }, "detour_minutes": { "description": "Extra travel time of visiting this place, in minutes (rounded to\n0.1): (origin→place) + (place→destination) − (origin→destination),\nall computed by the routing engine with the request's costing.", "format": "double", "type": "number" }, "detour_s": { "description": "The same detour in raw seconds.", "format": "double", "type": "number" }, "fuel_brand": { "description": "The matched fuel station's brand, when known (same conditions as\n`fuel_prices`).", "type": [ "string", "null" ] }, "fuel_prices": { "description": "Live pump prices for this fuel/petrol station, keyed by fuel code\n(e.g. \"diesel\", \"petrol_95\"), each `{value, currency, updated_at}`.\nOnly ever present when the server is gateway-preferred AND the\ndeployment configured `SN_FUEL_PRICES` AND this result matched a\nstation within range — never fabricated. Direct-backend fallback\nnever sets this (see the gap this closes in the PR description)." }, "fuel_updated_at": { "description": "When the matched fuel station's prices were last refreshed (RFC\n3339), when known (same conditions as `fuel_prices`).", "type": [ "string", "null" ] }, "off_route_m": { "description": "Straight-line distance from the place to the route, metres.", "format": "double", "type": "number" }, "place": { "$ref": "#/$defs/GeocodeHit", "description": "The place (geocoder hit: name, label, coordinates, type)." } }, "required": [ "place", "detour_minutes", "detour_s", "along_route_position", "off_route_m" ], "type": "object" }, "GeocodeHit": { "description": "One geocoding result.", "properties": { "city": { "description": "City or town, when known.", "type": [ "string", "null" ] }, "country": { "description": "Country, when known.", "type": [ "string", "null" ] }, "label": { "description": "Human-readable one-line label assembled from the address parts.", "type": "string" }, "lat": { "description": "Latitude in decimal degrees.", "format": "double", "type": "number" }, "lon": { "description": "Longitude in decimal degrees.", "format": "double", "type": "number" }, "match": { "anyOf": [ { "$ref": "#/$defs/GeocodeMatch" }, { "type": "null" } ], "description": "How far this hit can be trusted to be the place that was asked\nfor — see [`GeocodeMatch`]. Present whenever the MapMap gateway\nanswered; absent on a deployment falling back to the direct Photon\ngeocoder, and absent on the gateway's own fast paths (a pasted\ncoordinate pair, a bare UK outward code, a category browse), which\nanswer without a ranking to report on." }, "name": { "description": "Place name, when the source feature has one.", "type": [ "string", "null" ] }, "postcode": { "description": "Postcode, when known.", "type": [ "string", "null" ] }, "type": { "description": "Feature type, e.g. \"house\", \"street\", \"city\" (falls back to the\nOSM value when the endpoint does not classify).", "type": [ "string", "null" ] } }, "required": [ "label", "lat", "lon" ], "type": "object" }, "GeocodeMatch": { "description": "How well one geocoding result answers what was actually asked.\n\nGeocoding's real failure mode is not \"no answer\" but a confident answer\nto a different question: a plausible row on the wrong street, with\nnothing in the response to say so. This object is that missing say-so,\nand an agent should read it before acting on an address.\n\nHow to read it:\n\n* Any component `unmatched` or `inferred` on the TOP hit means the\n answer does not carry the address that was asked for — an `unmatched`\n postcode means the result has no postcode at all, `inferred` means it\n has a different one. Neither is a match. Say so rather than presenting\n the hit as the address, and reach for `verify_places` when the address\n came from a model or a user and needs checking rather than using.\n* A small `score_gap` means the ranking barely chose between this hit\n and the runner-up, which is exactly when to show the alternatives\n instead of picking one for the user.", "properties": { "components": { "$ref": "#/$defs/GeocodeMatchComponents", "description": "Per-component verdict on this hit: one entry for each structured\ncomponent supplied, and empty when the query was free text only." }, "score_gap": { "description": "The top result's score minus the runner-up's, rounded to 3 decimal\nplaces. `0` for a single result, and `0` from the `photon` source,\nwhich publishes no per-result score — so a `0` is \"no signal\", not\n\"a tie\".", "format": "double", "type": "number" }, "source": { "description": "Which backend answered: \"mapmap-index\" (the first-party index) or\n\"photon\".", "type": "string" } }, "required": [ "components", "score_gap", "source" ], "type": "object" }, "GeocodeMatchComponents": { "description": "Per-component verdicts inside a [`GeocodeMatch`]. Each is one of\n\"matched\", \"inferred\" or \"unmatched\"; a component that was not supplied\nis absent entirely.\n\n* \"matched\" — the result's own field carries the value asked for (case-\n and accent-insensitive, and by containment, so `city: \"London\"`\n matches \"City of London\").\n* \"inferred\" — the result carries a value for that component, but not\n the one asked for. It reached the page through ranking, as when a\n street is found by its transliterated name and displayed under its\n canonical one.\n* \"unmatched\" — the result carries no value for that component at all.", "properties": { "city": { "description": "Verdict on the supplied `city`.", "type": [ "string", "null" ] }, "country": { "description": "Verdict on the supplied `country`.", "type": [ "string", "null" ] }, "housenumber": { "description": "Verdict on the supplied `housenumber`.", "type": [ "string", "null" ] }, "postcode": { "description": "Verdict on the supplied `postcode`.", "type": [ "string", "null" ] }, "street": { "description": "Verdict on the supplied `street`.", "type": [ "string", "null" ] } }, "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "candidate_cap": { "description": "The matrix fan-out cap in force.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_considered": { "description": "Candidates found near the corridor before pricing.", "format": "uint", "minimum": 0, "type": "integer" }, "candidates_costed": { "description": "Candidates actually priced through the engine (fan-out is capped\nat `candidate_cap` nearest-to-route).", "format": "uint", "minimum": 0, "type": "integer" }, "costing": { "description": "The costing the detours were priced with.", "type": "string" }, "fuel_attribution": { "description": "Attribution string for fuel-price data sources, present only when\nat least one returned result carries `fuel_prices` (gateway-preferred\nmode with `SN_FUEL_PRICES` configured; see [`AlongRouteHit`]).", "type": [ "string", "null" ] }, "matched_categories": { "description": "The normalised category tokens the candidates were browsed by,\npresent only when a category browse actually ran (an explicit\n`category`, or a query the server promoted to one). Absent means\nfree-text name matching answered the call, so a caller can tell how\nits words were understood rather than inferring it from the results.", "items": { "type": "string" }, "type": [ "array", "null" ] }, "max_detour_minutes": { "description": "The detour budget applied, minutes.", "format": "double", "type": "number" }, "query": { "description": "The query as interpreted.", "type": "string" }, "results": { "description": "Places within the detour budget, cheapest detour first.", "items": { "$ref": "#/$defs/AlongRouteHit" }, "type": "array" }, "route_distance_m": { "description": "Direct origin→destination distance in metres.", "format": "double", "type": [ "number", "null" ] }, "route_duration_s": { "description": "Direct origin→destination travel time in seconds (same estimator\nas the detour legs), when routable.", "format": "double", "type": [ "number", "null" ] }, "route_length_m": { "description": "Length of the route geometry in metres.", "format": "double", "type": "number" } }, "required": [ "query", "costing", "route_length_m", "candidates_considered", "candidates_costed", "candidate_cap", "max_detour_minutes", "results" ], "type": "object" } }, { "description": "Set one MapLibre paint property on one skeleton layer of a hosted style (e.g. layer_id \"road-major\", property \"line-width\", value 4 or an expression array) and publish the result as a new immutable style version. Layer ids come from list_style_layers. Returns the new version and style URL.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "layer_id": { "description": "Skeleton layer id, e.g. \"road-major\". Call `list_style_layers`\nfor the accepted ids.", "type": "string" }, "property": { "description": "MapLibre paint property name, e.g. \"line-width\" or \"fill-color\".", "type": "string" }, "style_id": { "description": "Hosted style id.", "type": "string" }, "value": { "description": "The paint value (any MapLibre-valid JSON: number, colour string or\nexpression array)." } }, "required": [ "style_id", "layer_id", "property", "value" ], "type": "object" }, "name": "set_layer_paint", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "Hosted style id.", "type": "string" }, "style_url": { "description": "Immutable URL of the compiled style at this version.", "type": "string" }, "version": { "description": "The newly published version.", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "style_id", "version", "style_url" ], "type": "object" } }, { "description": "Recolour one or more palette slots of a hosted style (e.g. {\"water\": \"#0b2038\", \"roadMajor\": \"#8a6d3b\"}) and publish the result as a new immutable style version. Slot names come from list_style_layers; colours are CSS (#rgb/#rrggbb/#rrggbbaa/rgb()/hsl()). Returns the new version and style URL.", "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "colours": { "additionalProperties": { "type": "string" }, "description": "Palette overrides: slot name → CSS colour (e.g.\n{\"water\": \"#0b2038\"}). Call `list_style_layers` for the slot names.", "type": "object" }, "style_id": { "description": "Hosted style id.", "type": "string" } }, "required": [ "style_id", "colours" ], "type": "object" }, "name": "set_palette", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "style_id": { "description": "Hosted style id.", "type": "string" }, "style_url": { "description": "Immutable URL of the compiled style at this version.", "type": "string" }, "version": { "description": "The newly published version.", "format": "uint64", "minimum": 0, "type": "integer" } }, "required": [ "style_id", "version", "style_url" ], "type": "object" } }, { "description": "OPERATOR WRITE, ADMIN ONLY. Asserts a short-lived closure (`assert: {kind: closed}`) or speed (`{kind: speed_override, speed_kph}`, 1 to 120) on named OSM `way_ids` (1 to 20) and `direction` (forward, backward or both, default both), for `ttl_secs` (60 to 86400), with a `reason` and an optional `evidence_url`. The record expires on its own, ranks below official closures and is logged with the reason. NEVER CALL THIS WITHOUT A PERSON'S EXPLICIT APPROVAL OF THIS EXACT CLOSURE IN THIS CONVERSATION: draft it, show it, stop, and call it only after a yes. Available only where the operator runs sn-mcp locally with SN_CAMERA_INCIDENTS_ENABLED=true and SN_ADMIN_TOKEN set; everywhere else it refuses and says why. Whether a live traffic writer applies it is reported in `applied_by_live_writer`; false means the record is stored but no road changes.", "inputSchema": { "$defs": { "ClosureAssertion": { "description": "What a temporary closure asserts.", "oneOf": [ { "description": "Hard exclusion.", "properties": { "kind": { "const": "closed", "type": "string" } }, "required": [ "kind" ], "type": "object" }, { "description": "A speed, km/h.", "properties": { "kind": { "const": "speed_override", "type": "string" }, "speed_kph": { "description": "The speed.", "format": "uint32", "minimum": 0, "type": "integer" } }, "required": [ "kind", "speed_kph" ], "type": "object" } ] }, "Direction": { "description": "Carriageway of a way, spelled as the artefact spells it. A serde\ntwin of [`WayDirection`], which deliberately carries no serde.", "oneOf": [ { "const": "forward", "description": "Along the way's node order.", "type": "string" }, { "const": "backward", "description": "Against it.", "type": "string" }, { "const": "both", "description": "Both carriageways.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "assert": { "$ref": "#/$defs/ClosureAssertion", "description": "`closed` or a speed in km/h (1..=120)." }, "direction": { "$ref": "#/$defs/Direction", "default": "both", "description": "Carriageway, default `both`." }, "evidence_url": { "description": "Evidence link, if any.", "type": [ "string", "null" ] }, "reason": { "description": "Why, for the audit log and the incidents endpoint note.", "type": "string" }, "ttl_secs": { "description": "Lifetime, seconds, 60..=86400.", "format": "uint64", "minimum": 0, "type": "integer" }, "way_ids": { "description": "OSM way ids to assert on; at most 20.", "items": { "format": "uint64", "minimum": 0, "type": "integer" }, "type": "array" } }, "required": [ "way_ids", "assert", "ttl_secs", "reason" ], "type": "object" }, "name": "set_temporary_closure", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "applied_by_live_writer": { "description": "Whether a live writer is armed to apply it on this deployment. A\nrecord written where no writer runs is honest about it.", "type": "boolean" }, "end": { "description": "Lifts at, RFC 3339.", "type": "string" }, "id": { "description": "The override record id written.", "type": "string" }, "source": { "description": "Always `ops-admin`: the static admin token carries no key id.", "type": "string" } }, "required": [ "id", "source", "end", "applied_by_live_writer" ], "type": "object" } }, { "description": "Send MapMap a structured integration retro (problems, gotchas, wins, docs gaps). Call at most once, after your MapMap integration works or you stop trying, and only if the developer has approved sending feedback to MapMap. Sends ONLY the structured fields in this schema to MapMap: there is no field for a transcript, a prompt, source code, file contents or coordinates. The free-text fields are short and capped, but they are still free text: do NOT paste code, credentials, customer names or personal data into them. Provide `what_built` (required), `problems` [{area: sdk|api|mcp|docs|billing|self-host|other, description, workaround_found}], `gotchas`, `wins`, `docs_gaps`, and optionally `agent_name` and `sdk_version`.", "inputSchema": { "$defs": { "RetroProblemArea": { "description": "The platform area an integration problem belongs to (mirrors the\ngateway's `POST /v1/feedback` schema).", "oneOf": [ { "const": "sdk", "description": "The mobile/web SDKs.", "type": "string" }, { "const": "api", "description": "The hosted HTTP API.", "type": "string" }, { "const": "mcp", "description": "The MCP tool surface.", "type": "string" }, { "const": "docs", "description": "Documentation.", "type": "string" }, { "const": "billing", "description": "Billing, keys or quotas.", "type": "string" }, { "const": "self-host", "description": "Self-hosted deployment.", "type": "string" }, { "const": "other", "description": "Anything else.", "type": "string" } ] }, "RetroProblemInput": { "description": "One problem hit during the integration.", "properties": { "area": { "$ref": "#/$defs/RetroProblemArea", "description": "Which part of the platform the problem was in." }, "description": { "description": "What went wrong (at most 1000 bytes).", "type": "string" }, "workaround_found": { "description": "Whether a workaround was found.", "type": "boolean" } }, "required": [ "area", "description", "workaround_found" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "agent_name": { "description": "The submitting agent's name, e.g. \"Claude Code\" (at most 100\nbytes).", "type": [ "string", "null" ] }, "docs_gaps": { "default": [], "description": "Documentation gaps hit (at most 20 entries × 500 bytes).", "items": { "type": "string" }, "type": "array" }, "gotchas": { "default": [], "description": "Surprises/traps worth documenting (at most 20 entries × 500 bytes).", "items": { "type": "string" }, "type": "array" }, "problems": { "default": [], "description": "Problems hit during the integration (at most 20).", "items": { "$ref": "#/$defs/RetroProblemInput" }, "type": "array" }, "sdk_version": { "description": "MapMap SDK version integrated against, when known (at most 50\nbytes).", "type": [ "string", "null" ] }, "what_built": { "description": "What was built with MapMap, in one or two sentences (required, at\nmost 500 bytes).", "type": "string" }, "wins": { "default": [], "description": "What went well (at most 20 entries × 500 bytes).", "items": { "type": "string" }, "type": "array" } }, "required": [ "what_built" ], "type": "object" }, "name": "submit_integration_retro", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "delivery": { "description": "How it was delivered: `gateway` or `local-queue`.", "type": "string" }, "message": { "description": "A short, honest sentence for the agent saying where the retro\nlanded — sent to MapMap, or held in this server's local queue.", "type": "string" }, "retro_id": { "description": "Id of the stored retro (gateway id, or the local queue id).", "type": "string" }, "status": { "description": "`received` when the gateway stored it; `queued` when it was\nappended to the local review queue.", "type": "string" } }, "required": [ "retro_id", "status", "delivery", "message" ], "type": "object" } }, { "description": "Submit a problem too large to solve inside one request to the asynchronous lane, and get a job id back. Set `kind` to \"optimise\", \"replan\" or \"matrix\", and pass `problem` in EXACTLY the shape the matching synchronous tool takes — `optimise_routes` input, `replan_routes` input, or `matrix` input. Moving a working synchronous call onto this lane changes nothing but which tool you call it with. A field that tool's input does not have is REFUSED by name rather than dropped: the HTTP API accepts some the MCP tools have not surfaced yet, and a job queued without a constraint you asked for is worse than one that was never queued. The ceilings are far higher here because there is no request to hold open: 2,000 unique locations for an optimisation or re-plan against the synchronous 200, and 40,000 matrix elements against 10,000 (a deployment may set either lower, in which case its own refusal is the authority). A re-plan is counted on the REMAINING problem, after completed stops are removed, so a shift well through its day may fit where the morning's would not. This answers 202-and-a-job-id, NOT a plan: the job is queued and a worker picks it up. Poll `get_job` with the returned id until it says the status is terminal, then read the result. Polling is free — the gateway meters this submission, not the reads. Units are charged on submission and handed back in full if the job fails. The optional `webhook_url` (https only) posts a SIGNED notification when the job finishes and is for a human wiring infrastructure that must react without a process watching; it carries a pointer, never the result, and needs a webhook signing secret on the key. An agent that can poll should not use it. Requires the MapMap gateway.", "inputSchema": { "$defs": { "JobKind": { "description": "Which asynchronous problem is being submitted.", "oneOf": [ { "const": "optimise", "description": "A fleet optimisation — the `optimise_routes` problem, at ten times\nthe synchronous location cap.", "type": "string" }, { "const": "replan", "description": "A mid-shift re-plan — the `replan_routes` problem, counted on the\nremaining work.", "type": "string" }, { "const": "matrix", "description": "A many-to-many time/distance matrix — the `matrix` problem, at four\ntimes the synchronous element cap.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "kind": { "$ref": "#/$defs/JobKind", "description": "Which problem this is. It selects both the body shape below and the\nceiling the submission is judged against." }, "problem": { "description": "The problem itself, in exactly the shape the synchronous tool takes\n— `optimise_routes` input for `optimise`, `replan_routes` input for\n`replan`, `matrix` input for `matrix`. Moving a working synchronous\ncall onto this lane changes nothing but the tool you call it with." }, "webhook_url": { "description": "Optional HTTPS URL to POST a signed `{job_id, kind, status,\nresult_url}` notification to when the job finishes. The RESULT is\nnever pushed — the notification says where to fetch it. Requires a\nwebhook signing secret on the key; without one the submission is\nrefused rather than delivered unsigned. An agent that can poll does\nnot need this: polling with `get_job` is free.", "type": [ "string", "null" ] } }, "required": [ "kind", "problem" ], "type": "object" }, "name": "submit_optimise_job", "outputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "id": { "description": "The job id. Pass it to `get_job` to poll.", "type": "string" }, "kind": { "description": "`optimise`, `replan` or `matrix`.", "type": "string" }, "next": { "description": "What to do next, in one sentence: poll `get_job`, and how often is\nreasonable.", "type": "string" }, "result_url": { "description": "The HTTP URL this job (and its result) can be fetched from. The\nsame URL a webhook carries. `get_job` is the tool that reads it.", "type": "string" }, "status": { "description": "Always `queued` — no worker has looked at it yet.", "type": "string" }, "units_charged": { "description": "Quota units this submission drew. Charged now, handed back in full\nif the job fails.", "format": "int64", "type": "integer" } }, "required": [ "id", "kind", "status", "result_url", "units_charged", "next" ], "type": "object" } }, { "description": "Check whether a dataset's DECLARED coordinate reference system actually describes its own coordinates, before you draw it on a map. Catches the failures that are otherwise silent: swapped lat/lon axes, degrees labelled as metres, and Web Mercator or another projection mislabelled with a UTM or national-grid code. Pass the declared CRS (e.g. \"EPSG:4326\") and a sample of the raw coordinates as {x, y} in the dataset's OWN units — deliberately not named lon/lat, because whether they are degrees is the question. Returns a verdict (consistent / suspect / impossible), what is wrong in plain language, and where the numbers actually point when read another way. This is a sanity check, not a reprojection: it never transforms coordinates. Local computation: no network call, no quota.", "inputSchema": { "$defs": { "XY": { "description": "One coordinate from the dataset, in the dataset's own units — NOT\nnecessarily degrees. Named `x`/`y` rather than `lon`/`lat` precisely\nbecause whether they are degrees is the thing in question.", "properties": { "x": { "format": "double", "type": "number" }, "y": { "format": "double", "type": "number" } }, "required": [ "x", "y" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "coordinates": { "description": "A sample of the dataset's coordinates. A few dozen is plenty; the\ncheck is about ranges and spans, not volume.", "items": { "$ref": "#/$defs/XY" }, "type": "array" }, "declared_crs": { "description": "The CRS the dataset claims, e.g. `\"EPSG:4326\"`, `\"EPSG:32610\"`,\n`\"EPSG:3857\"`, `\"EPSG:27700\"`.", "type": "string" } }, "required": [ "declared_crs", "coordinates" ], "type": "object" }, "name": "validate_geodata", "outputSchema": { "$defs": { "Extent": { "properties": { "max_x": { "format": "double", "type": "number" }, "max_y": { "format": "double", "type": "number" }, "min_x": { "format": "double", "type": "number" }, "min_y": { "format": "double", "type": "number" } }, "required": [ "min_x", "max_x", "min_y", "max_y" ], "type": "object" }, "Verdict": { "description": "How much the declared CRS and the coordinates disagree.", "oneOf": [ { "const": "consistent", "description": "Coordinates are consistent with the declared CRS.", "type": "string" }, { "const": "suspect", "description": "Consistent only under an assumption worth stating (e.g. the values\nfit, but the axis order looks swapped).", "type": "string" }, { "const": "impossible", "description": "The declared CRS cannot describe these coordinates at all.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "extent": { "$ref": "#/$defs/Extent", "description": "Observed extent of the sample, in the dataset's own units." }, "interpreted_as": { "description": "The CRS family the declaration was understood as.", "type": "string" }, "problems": { "description": "Plain-language findings, most important first. Empty when consistent.", "items": { "type": "string" }, "type": "array" }, "suggestions": { "description": "What the numbers look like, when they do not match the declaration.", "items": { "type": "string" }, "type": "array" }, "verdict": { "$ref": "#/$defs/Verdict" } }, "required": [ "verdict", "interpreted_as", "problems", "suggestions", "extent" ], "type": "object" } }, { "description": "Check whether places (and itineraries) an AI mentioned are real, findable and physically possible. Pass structured `claims` (reliable, and the only path that supports itinerary feasibility) or free `text` (best-effort quoted-phrase extraction). Each claim resolves to exactly one of three verdicts, never a boolean: \"verified\" (matched a real place, with its stable id and the source/date of the evidence), \"contradicted\" (a specific, dated, sourced fact rules it out — currently only an itinerary leg the routing engine proves cannot be driven in the stated time, with the computed travel time as evidence), or \"unverified\" (no evidence either way). This tool NEVER asserts that a named real business does not exist or has closed — that would be a defamation risk with no upside; a missing match is always \"unverified\". Claims sharing increasing `sequence` values and both carrying `claimed_time` (ISO 8601) form itinerary legs checked for feasibility via `matrix`, catching e.g. \"breakfast in Bath, 10am meeting in Edinburgh\". Max 20 claims per request. The response's `summary` field is a concise plain-text digest — also returned as this tool result's text content — so clients that drop structured/non-text content blocks still see the verdicts.", "inputSchema": { "$defs": { "CostingKind": { "description": "Costing models exposed by the MCP tools (a deliberate subset of the\nValhalla costing list), serialised in snake_case exactly as Valhalla\nnames them.", "oneOf": [ { "const": "auto", "description": "Standard car costing.", "type": "string" }, { "const": "truck", "description": "Truck costing; honours dimensional limits and, when a `truck`\nprofile is supplied, ADR dangerous-goods restrictions.", "type": "string" }, { "const": "bicycle", "description": "Bicycle costing; tune it with a `bicycle` options object.", "type": "string" }, { "const": "pedestrian", "description": "Pedestrian (walking) costing; tune it with a `pedestrian` options\nobject (lit streets, wheelchair/blind, hiking difficulty).", "type": "string" }, { "const": "motor_scooter", "description": "Motor scooter (moped) costing: like auto but prefers lower-speed\nroads and may use ways closed to larger motor vehicles.", "type": "string" } ] }, "VerifyClaimInput": { "description": "One place claim for the `verify_places` tool: a place an AI mentioned,\nto be checked for existence and (as part of an itinerary) feasibility.", "properties": { "claimed_time": { "description": "ISO 8601 timestamp: when the itinerary claims you are at this place.", "type": [ "string", "null" ] }, "id": { "description": "Caller-chosen id, echoed back on the matching result. Auto-assigned\n(\"claim-1\", …) when omitted.", "type": [ "string", "null" ] }, "locality": { "description": "Optional disambiguating context, e.g. \"Oxford\". Country-level words\n(\"UK\", \"England\", …) are stripped before querying — they add noise,\nnot signal, to name search.", "type": [ "string", "null" ] }, "name": { "description": "The place name as claimed, e.g. \"The Eagle and Child\".", "type": "string" }, "sequence": { "description": "Itinerary position. Claims that share increasing `sequence` values\nand both carry `claimed_time` form legs the feasibility pass checks.", "format": "int64", "type": [ "integer", "null" ] } }, "required": [ "name" ], "type": "object" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "claims": { "description": "Structured claims — the reliable path, and the only path that\nsupports itinerary feasibility. Mutually exclusive with `text`.\nMax 20 per request.", "items": { "$ref": "#/$defs/VerifyClaimInput" }, "type": [ "array", "null" ] }, "costing": { "$ref": "#/$defs/CostingKind", "default": "auto", "description": "Costing for feasibility legs: \"auto\" (default), \"truck\", \"bicycle\",\n\"pedestrian\" or \"motor_scooter\"." }, "text": { "description": "Free text to extract place claims from (best-effort heuristic:\nquoted phrases and Title Case runs after \"at/in/near/to/from/\nvisiting\" — not NLP or an LLM call, and it does not attempt\nitinerary feasibility since there are no explicit times to anchor\nlegs to). Mutually exclusive with `claims`. Max 8,000 characters.", "type": [ "string", "null" ] } }, "type": "object" }, "name": "verify_places", "outputSchema": { "$defs": { "VerifyClaimEcho": { "description": "The claim as echoed back (a subset of the input, for context).", "properties": { "locality": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "sequence": { "format": "int64", "type": [ "integer", "null" ] } }, "required": [ "name" ], "type": "object" }, "VerifyClaimResult": { "description": "The result for one claim.", "properties": { "claim": { "$ref": "#/$defs/VerifyClaimEcho" }, "evidence": { "items": { "$ref": "#/$defs/VerifyEvidence" }, "type": "array" }, "feasibility": { "anyOf": [ { "$ref": "#/$defs/VerifyFeasibility" }, { "type": "null" } ] }, "id": { "type": "string" }, "match": { "anyOf": [ { "$ref": "#/$defs/VerifyPlaceMatch" }, { "type": "null" } ] }, "verdict": { "$ref": "#/$defs/VerifyVerdict" } }, "required": [ "id", "claim", "verdict", "evidence" ], "type": "object" }, "VerifyEvidence": { "description": "One piece of evidence backing a verdict. Always dated.", "properties": { "checked_at": { "description": "ISO 8601 timestamp of when this evidence was gathered (a check\ntime, not necessarily the underlying map data's edit date).", "type": "string" }, "detail": { "description": "Short factual note, always from a fixed template (see\n`crate::verify`) — never free-form text that could assert\nnon-existence or closure.", "type": "string" }, "source": { "description": "Where the evidence came from, e.g. \"mapmap-geocode\", \"mapmap-matrix\".", "type": "string" } }, "required": [ "source", "checked_at", "detail" ], "type": "object" }, "VerifyFeasibility": { "description": "Feasibility of the leg arriving at this stop, when computable.", "properties": { "available_minutes": { "description": "Minutes the itinerary claims are available for this leg.", "format": "double", "type": "number" }, "from_id": { "type": "string" }, "from_name": { "type": "string" }, "status": { "$ref": "#/$defs/VerifyFeasibilityStatus" }, "to_id": { "type": "string" }, "to_name": { "type": "string" }, "travel_time_minutes": { "description": "Minutes the routing engine computed, or null when it could not\ncompute one at all (e.g. beyond its routable distance).", "format": "double", "type": [ "number", "null" ] } }, "required": [ "from_id", "from_name", "to_id", "to_name", "available_minutes", "status" ], "type": "object" }, "VerifyFeasibilityStatus": { "description": "Feasibility status of one itinerary leg.", "oneOf": [ { "enum": [ "feasible", "impossible" ], "type": "string" }, { "const": "implausible", "description": "Tight — flagged, but not asserted as impossible (not certain enough\nto contradict).", "type": "string" } ] }, "VerifyPlaceMatch": { "description": "The place a claim matched, when one was found.", "properties": { "category": { "type": [ "string", "null" ] }, "id": { "description": "Stable identifier when the geocoding backend supplies one,\notherwise a coordinate-based fallback.", "type": "string" }, "lat": { "format": "double", "type": "number" }, "lon": { "format": "double", "type": "number" }, "name": { "type": "string" } }, "required": [ "id", "name", "lat", "lon" ], "type": "object" }, "VerifyVerdict": { "description": "The three-state verdict — see `crate::verify` module docs for why\nthere is no fourth \"does not exist\" state and never a boolean.", "oneOf": [ { "const": "verified", "description": "Matched a real, findable place in the index.", "type": "string" }, { "const": "contradicted", "description": "A specific, dated, sourced fact contradicts the claim (currently:\nan itinerary leg the routing engine proves cannot be driven in the\nstated time). Never used to assert a business does not exist or\nhas closed.", "type": "string" }, { "const": "unverified", "description": "No evidence either way: no confident name match, or the check\ncould not run.", "type": "string" } ] } }, "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { "results": { "items": { "$ref": "#/$defs/VerifyClaimResult" }, "type": "array" }, "summary": { "description": "Always present: a concise plain-text summary alongside the\nstructured `results` — several MCP clients (notably ChatGPT\nconnectors) drop non-text content blocks, so this must stand on\nits own. This same string is also returned as the tool call's text\ncontent block, not only inside the structured JSON.", "type": "string" } }, "required": [ "results", "summary" ], "type": "object" } } ] }
Verify it yourselfcurl -s https://api.teppi.xyz/v1/evidence/sha256:a27531f21cebc40bf64379b758b32f4fa1c72c0f6a3841dbc0cced0e19c8cfbc | sha256sum