Data catalog
Count and sum a cloud GeoParquet per zone
A count, sum or average over a large public GeoParquet for a place or per zone (state, county, region, district), without downloading it.
| REST | POST https://mcp.terra-lab.ai/v1/tools/aggregate_dataset |
| MCP tool | aggregate_dataset on https://mcp.terra-lab.ai/mcp |
| Group | Data catalog |
Request
curl -X POST https://mcp.terra-lab.ai/v1/tools/aggregate_dataset \
-H "Authorization: Bearer $TERRALAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dataset":"portolan:harmonized-field-data/glocab","zones":{"per":"state","units":["Arkansas"]}}'
import os
import requests
response = requests.post(
"https://mcp.terra-lab.ai/v1/tools/aggregate_dataset",
headers={"Authorization": f"Bearer {os.environ['TERRALAB_API_KEY']}"},
json={
"dataset": "portolan:harmonized-field-data/glocab",
"zones": {
"per": "state",
"units": ["Arkansas"],
},
},
timeout=120,
)
response.raise_for_status()
print(response.json()["structuredContent"])
const response = await fetch('https://mcp.terra-lab.ai/v1/tools/aggregate_dataset', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TERRALAB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"dataset": "portolan:harmonized-field-data/glocab",
"zones": {
"per": "state",
"units": [
"Arkansas"
]
}
}),
});
const data = await response.json();
console.log(data.structuredContent);
Other valid request bodies:
{"dataset":"portolan:nasa-firms-active-fire/detections","zones":{"per":"county","within":"California"},"where":[{"column":"year","op":"eq","value":2024}]}
Parameters
A JSON object in the request body; in an MCP client, the arguments of the tool call.
| Name | Type | Required | Description |
|---|---|---|---|
dataset | string | Yes | A portolan: id from find_datasets (open_registry), or the https URL of a public GeoParquet file, a hive glob such as https://.../year=*/data.parquet, an s3:// or gs:// address, or a STAC collection.json. Up to 400 characters. |
area | object | No | The area: a bbox, or a place (name, address or coordinates) with an optional radius in metres. The place the user asks about, not the user's own location. |
area.bbox | array of number | No | [west, south, east, north] in decimal degrees (WGS 84). Exactly 4 items. |
area.place | string or object | No | A place name or address, or a point. |
area.radius_m | number | No | Radius around the place in metres. From 1 to 100000. |
zones | object | No | One row per zone instead of one area. |
zones.per | string | Yes | The zone level. One of "state", "county", "zip", "place", "country", "region", "locality", "neighborhood", "adm1", "adm2", "adm3", "adm4". |
zones.within | string | No | The place whose zones are read: "California", "Kenya", or a zone id (US-STATE-06). Up to 200 characters. |
zones.units | array of string | No | Only these zones, by name or code, at the level per gives. 0 to 200 items. |
where | array of object | No | Conditions that must all hold. A condition on a partition key (year) picks the files before any byte is read; without one, only the newest partition is read. 0 to 12 items. |
group_by | array of string | No | Columns to group by, one row per value (per zone). 0 to 2 items. |
aggregates | array of object | No | Values beside the count: sum, mean, min or max of a column; area_m2 of the geometries. 0 to 8 items. |
limit | integer | No | Rows returned, largest count first; distinct_groups says how many there are. From 1 to 100. Default 50. |
dry_run | boolean | No | Only the plan: files, row groups and bytes the query would read. Default false. |
Response
structuredContent holds the result, content the same result as text for a model, and isError is true when the tool refused the request or failed; then structuredContent.code, message and hint say what to change.
{
"structuredContent": {
"sources": [{ "name": "...", "licence": "...", "url": "..." }],
"downloads": [{ "label": "...", "format": "...", "url": "...", "name": "..." }]
},
"content": [{ "type": "text", "text": "..." }],
"isError": false
}
Every result lists its sources with their licence. File results come as downloads: each link builds its file when opened.
Errors
A missing key answers 401, an empty balance 402, too many calls 429. A call that fails or finds nothing is not counted. See Errors and rate limits.
Description for agents
The text the server gives an agent to decide when to call this tool:
Use this when the user wants a count, sum or average over a large public GeoParquet for a place or per zone, without downloading it: "how many crop fields are in Arkansas?", "active fires per county in California in 2024", "total building footprint area in Berlin Mitte", "fields per crop type". Give dataset (a portolan: id from find_datasets, or a GeoParquet URL; inspect_dataset lists its columns and partitions), then the area: area (a named place is cut to its outline; a place with radius_m is a disk; a bbox), or zones for one row per zone (per: state, county, zip, place in the US; country, region, locality, neighborhood anywhere; adm1 to adm4 official units; within: the parent place, or units: the zones by name). Without either, the whole file. where filters rows (a condition on the partition key, such as year, picks the files; without one only the newest partition is read). group_by gives one row per value (up to 2 columns); aggregates adds sum, mean, min or max of a column, or area_m2 of the geometries (reads the geometry, heavier). A row counts in a zone when the centre of its bounding box lies inside. Only the row groups the zones touch are read (a state from a multi-GB file in seconds); a query over the caps gets too_large with the estimate. Returns the table (zone, group, count, values), totals, distinct_groups and what was read. No features: extract_dataset cuts them. Never use the user's current location unless they type it. Give the place as the user names it (place name, address or coordinates): this tool locates it, so find_places is not needed first.
Updated 2026-11-15