A retail siting team picks a candidate location. Before the next meeting they need: the drive-time trade area, the total population inside it, and the median household income. geo_isochrone + population_in_polygon answers in two calls.
mcp.call("geo_isochrone", {
point: [-77.9111, 34.1659], //
minutes: [15], // single ring
profile: "driving",
})
// → GeoJSON polygon — the reachable 15-min driving footprint
One call, area-weighted from Census tracts. The system cache covers ~20 headline ACS variables (population, income, age, race breakdowns, education, housing). Variables in the cache cost nothing extra; variables outside it fall back to the Census API automatically.
mcp.call("population_in_polygon", {
geometry: isochrone.geometry,
variables: [
"B01001_001E", // total population
"B19013_001E", // median household income
"B01002_001E", // median age
"B15003_022E", // bachelor's degree+
"B25077_001E", // median home value
],
year: 2023,
})
// → {
// variables: {
// B01001_001E: ,
// B19013_001E: ,
// B01002_001E: ,
// B15003_022E: ,
// B25077_001E: ,
// },
// intersecting_tract_count: ,
// tracts_with_data: ,
// methodology: "Area-weighted interpolation from us-tracts/tiger-2024.
// For each tract T that intersects the polygon,
// w = area(T ∩ polygon) / area(T).
// Extensive (counts): Σ tract.value × w.
// Intensive (rates/medians): Σ tract.value × w / Σ w.
// Variables sourced from system cache: .
// Variables sourced from live Census API: ."
// }
That's the answer. One call returns the demand-side variables you need to size the trade area. Total elapsed: under a second when the system Census cache is warm; a small Census API hit otherwise. The methodology string is suitable for the report's citation block — verbatim provenance for the joined numbers.
// Use the isochrone polygon as a one-off shape, fill with the population value.
mcp.call("ingest_geojson", {
url: null, inline: isochrone,
collection: "candidate-site-trade-area", version: "v1",
})
mcp.call("render_map", {
collection: "candidate-site-trade-area", version: "v1",
field: "B01001_001E", palette: "sequential",
title: "15-minute drive-time trade area",
subtitle: "Aggregated from intersecting Census tracts, area-weighted",
pin_overlays: [{
name: "Site",
color: "#dc2626",
points: [{ lng: -77.9111, lat: 34.1659, label: "" }],
}],
boundary_overlays: [{
collection: "us-counties", version: "tiger-2024",
stroke: "#475569", stroke_width: 1.0,
}],
})
The traditional answer: download isochrones from Mapbox or OpenRouteService, manually export to GeoJSON, ingest into PostGIS, find intersecting tracts via QGIS, pull ACS values per tract from data.census.gov, compute area-weighted aggregates in a spreadsheet. Half a day per site. Then re-do it when the boss asks for a 20-minute ring, or the next site, or median home value too. One call now replaces all of it. The system cache means even at scale (a chain evaluating 50 candidate sites) the Census API doesn't get hit at all.
Tools used: geo_isochrone, population_in_polygon, ingest_geojson, render_map (with pin_overlays + boundary_overlays). population_in_polygon is premium-gated. geo_isochrone requires a Mapbox or OpenRouteService key in the workspace's secrets.