Skip to main content
Glama
rteina

geoparquet-mcp

by rteina

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
GEOPARQUET_SOURCESNoComma-separated list of dataset names that the server can access. For example, 'overture_places' limits access to only that dataset. Defaults to all available datasets.
GEOPARQUET_ENABLE_MCPNoSet to 0 to disable the MCP sub-application, leaving a REST-only process.1

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
geoparquet_describe_sourceA

Describe one remote dataset without reading any of its data: column names and types, the geometry and bbox columns, the coordinate reference system, the exact row count, the number of Parquet parts and row groups, the total remote size, and the geographic extent the dataset covers.

WHEN TO USE IT. Call this before your first filter against a dataset. It is how you learn the real column names — Overture nests many of them, so a category is categories.primary and a label is names.primary, not category and name — and how you check that the region you care about is inside extent before spending a query on it.

PARAMETERS. source: dataset name, from the geoparquet://sources resource or the default. Every other tool takes the same name.

WHAT COMES BACK. columns is a list of {name, type, role}, where role marks the geometry, bbox, name, category and confidence columns. crs is the coordinate reference system (OGC:CRS84 means plain longitude/latitude degrees, which is what every tool here expects) and crs_is_default says whether the file stated it or inherited the GeoParquet default. extent is the dataset's bounding box, computed from row-group statistics, or null when the file carries no statistics to compute it from. row_count, remote_files, row_groups and remote_bytes describe the physical file. scan reports the bytes this call itself pulled: Parquet footers only, never a data page. On Overture places that is about 26 MB the first time — the footers of 16 parts carrying 4096 row groups of statistics — and zero afterwards, because the session caches them. Either way it is metadata about a 10.5 GB file, not the file.

geoparquet_preview_rowsA

Return the first few rows of a dataset, so you can see what the values actually look like.

WHEN TO USE IT. After geoparquet_describe_source tells you a column exists and before you filter on it, to learn how it is populated: what a category string looks like in practice, whether a field is mostly null, how an address is spelled. Guessing a filter value and getting zero features back costs more than one preview.

This is NOT a spatial question. The rows are whatever the file stores first, in no geographic order and in no ranking — do not read them as "the most important places" or "places near anywhere". To ask where things are, use geoparquet_filter_spatial or geoparquet_find_nearest.

PARAMETERS. source: dataset name. columns: column expressions to return, for example ["id", "names.primary", "confidence"]. Omit for the dataset's default projection. limit: how many rows, 1 to 100. Ten is usually enough to see the shape.

WHAT COMES BACK. rows as plain records, columns_returned naming the keys, and the scan block. The read stops at the first row group of the first part file, so the cost does not grow with the dataset.

geoparquet_filter_spatialA

Return the features of a dataset that fall inside an area, as a GeoJSON FeatureCollection. The area is either a lon/lat rectangle or an arbitrary WKT geometry.

WHEN TO USE IT. When the answer is the features themselves — "which cafes are in this neighbourhood", "give me the buildings along this street" — and you intend to look at them individually. When you only need a count, a ranking or a distribution, use geoparquet_aggregate_attribute or geoparquet_summarize_h3 instead: they answer from the remote file and transfer kilobytes instead of features.

COST. The rectangle is what makes the read cheap. It is pushed into the remote Parquet file and prunes whole row groups from their footer statistics before any byte of data is fetched, so a tight box costs far less than a wide one — this is the difference between megabytes and gigabytes, not a micro-optimisation. Always pass the tightest area the question allows.

PARAMETERS. source: dataset name. min_lon, min_lat, max_lon, max_lat: the rectangle, in WGS 84 degrees. Pass all four, or none if you are using wkt. wkt: an arbitrary geometry instead of a rectangle, for example 'POLYGON ((2.33 48.85, 2.36 48.85, 2.36 48.87, 2.33 48.87, 2.33 48.85))'. Its envelope prunes the read and the exact shape then filters the survivors, so the answer is exact. Give either a rectangle or a wkt, never both. category: exact match on the dataset's category column, for example 'restaurant'. Preview the column first — the vocabulary is not obvious. name_contains: case-insensitive substring of the feature name. min_confidence: 0 to 1, Overture's own confidence in the record. 0.8 drops most questionable entries. columns: column expressions to return. A narrow projection is worth as much as a tight box, because Parquet is columnar and unread columns are unfetched. include_geometry: false skips the geometry column — the widest in the file — and approximates each feature by its bounding-box corner, which is exact for points. Ignored when wkt is used, since the exact test needs the geometry. limit: maximum features, capped at 1000.

WHAT COMES BACK. geojson as a FeatureCollection; feature_count and truncated, which tells you the limit was reached and there is more; geometry_is_exact; the sql that ran; and scan with bytes_scanned — read it, and tighten the area if it looks large.

geoparquet_find_nearestA

Return the features closest to a point, nearest first, each with its great-circle distance in kilometres.

WHEN TO USE IT. For "what is near here" and "which is the closest" — the questions where the ranking and the distance are the answer. Use geoparquet_filter_spatial instead when you want everything in an area rather than the closest few.

HOW IT STAYS CHEAP. A radius is not something Parquet statistics can prune on, so the search circle is first widened to its bounding rectangle, which is prunable; the exact distance is then computed only over the rows that survive, and used both to filter and to order. A large radius therefore costs a large read: prefer the smallest radius that can contain the answer, and widen it only if you come back empty.

PARAMETERS. source: dataset name. lon, lat: the centre point, in WGS 84 degrees. Longitude first. radius_km: how far to look, up to 500. Results outside it are excluded, so this is a filter, not just a hint. category, name_contains: the same narrowing as geoparquet_filter_spatial. columns: column expressions to return. limit: how many neighbours, capped at 1000.

WHAT COMES BACK. rows, ordered nearest first, each carrying distance_km; the search_bbox actually used for pruning; and the scan block.

geoparquet_aggregate_attributeA

Group the rows of a dataset by one column and aggregate them, optionally inside a lon/lat rectangle. The grouping runs inside the remote file.

WHEN TO USE IT. For "how many of each", "what is the average", "which is the most common" — any question whose answer is a table of groups rather than a set of features. This is the tool that makes a 10 GB dataset answerable in kilobytes: the grouping happens remotely and only the group rows cross the network, however many rows went into them. Reach for it before geoparquet_filter_spatial whenever counting would do.

PARAMETERS. source: dataset name. group_by: the column whose distinct values become the groups, for example 'categories.primary'. aggregate: one of count, sum, avg, min, max. Default count. measure: the column to aggregate. Required for sum, avg, min and max, and rejected for count, which counts rows. It must be numeric; a non-numeric one is refused before any byte is fetched. min_lon, min_lat, max_lon, max_lat: restrict the aggregate to a rectangle. Pass all four or none. Omitting them aggregates the entire dataset, which reads the grouped and measured columns in full — slow and expensive on a multi-gigabyte source. Pass a box unless you truly mean the whole world. limit: maximum groups returned, ordered by the aggregate descending.

WHAT COMES BACK. groups, each with group_value, value (the aggregate) and row_count (rows in the group); rows_aggregated; truncated; the sql that ran; and the scan block.

geoparquet_summarize_h3A

Bin the features inside a rectangle into H3 hexagonal cells and return the count per cell. A density map, computed remotely.

WHEN TO USE IT. For "where are these densest", "how is this spread across the city", and anything you would answer with a heatmap. The binning and counting happen inside the remote file, so the answer is a few hundred cells whether they cover a thousand features or ten million — you never transfer the features to find out where they cluster.

PARAMETERS. source: dataset name. min_lon, min_lat, max_lon, max_lat: the rectangle to bin, in WGS 84 degrees. All four are required; this tool has no whole-world mode by design. resolution: the H3 level, 0 to 15. 0 is continent-sized, 6 is a city, 8 is roughly a neighbourhood, 9 a block, 11 a building. Choosing too fine a resolution for a wide box returns thousands of near-empty cells; start at 8 for a city and adjust. limit: maximum cells, ordered by count descending, so the limit keeps the hotspots. include_cell_centre: adds the latitude and longitude of each cell's centre, which is what you need to plot the result.

WHAT COMES BACK. cells, each with h3_cell (the canonical hexadecimal id), feature_count and optionally the centre; plus features_binned, truncated, and the scan block.

Features are binned on their bounding-box centre rather than their true geometry, which avoids fetching the widest column in the file: exact for point datasets, the envelope's centre for polygonal ones. Requires DuckDB's H3 extension; if it cannot be loaded the tool says so rather than falling back to something slower.

geoparquet_count_in_polygonsA

Count how many features of one dataset fall inside each polygon of another: a point-in-polygon join between two remote datasets, restricted to a rectangle.

WHEN TO USE IT. For "how many of these are in each district", "which neighbourhood has the most of them", "break this down by administrative area" — any question whose answer is a table of areas with a number against each. It is the only tool that reads two datasets at once, and the only way to group by something that is not a column but a shape.

Use geoparquet_aggregate_attribute instead when you can group by a column the dataset already carries; it is much cheaper. Use this one when the grouping is geographic and the boundaries live in a different file.

COST. This is the most expensive tool here, and knowingly so: the rectangle prunes both datasets before the join, but the containment test still has to decode real geometry on both sides. Expect tens of megabytes and tens of seconds on a city-sized box, against single-digit megabytes for the other tools. Keep the rectangle tight, and prefer a narrower polygon_subtype.

PARAMETERS. min_lon, min_lat, max_lon, max_lat: the rectangle, in WGS 84 degrees. All four are required — this tool has no whole-world mode. point_source: the dataset being counted. polygon_source: the dataset providing the containing areas. It must be a polygonal dataset; a point dataset is refused before anything is read. polygon_subtype: narrows the polygon side to one administrative level, for example 'locality' or 'county'. Without it a country-sized polygon is returned alongside a neighbourhood one, because both overlap the rectangle, and the counts are then not comparable to each other. limit: maximum polygons returned, ordered by count descending.

WHAT COMES BACK. polygons, each with polygon_name, polygon_subtype and feature_count; the sql that ran; and the scan block. The count is the number of features whose geometry is contained by that polygon, not merely overlapping its bounding box.

geoparquet_run_sqlA

Run one read-only SELECT against the datasets in scope, for questions the other tools do not have a shape for.

WHEN TO USE IT. Last, not first. The typed tools push their filters into the remote Parquet file by construction; an ad-hoc query pushes down only what its WHERE clause happens to express, so a query that forgets a bbox predicate can read gigabytes to answer something geoparquet_aggregate_attribute would have answered in kilobytes. Reach for it for genuine gaps: a join between two datasets, a HAVING clause, a window function, a self-join.

WHAT YOU CAN QUERY. Each dataset in scope is a table named exactly as the dataset is — overture_places, overture_divisions, overture_buildings — and those are the only tables that exist. There is no way to name a file: table functions such as read_parquet are refused, and so is anything that is not a single SELECT. That is a perimeter, not a lint rule.

WRITING A FAST ONE. Constrain bbox explicitly, as four comparisons on its members, because that is the form Parquet statistics can prune on:

SELECT categories.primary AS category, count(*) AS n FROM overture_places WHERE bbox.xmin <= 2.40 AND bbox.xmax >= 2.30 AND bbox.ymin <= 48.88 AND bbox.ymax >= 48.85 GROUP BY 1 ORDER BY n DESC

Writing that filter with a geometry function instead would be correct and would read the entire file, because the Parquet reader cannot see through it. Select named columns rather than *, for the same reason: unread columns are unfetched.

PARAMETERS. sql: one SELECT statement. max_rows: row ceiling, applied as an outer LIMIT. Hard, and capped at 1000. max_bytes: byte ceiling. Reported, not pre-emptive — see below.

WHAT COMES BACK. rows, tables_read, the executed_sql actually run, the scan block, and byte_budget_exceeded. That last one is a verdict after the fact, not a brake: DuckDB cannot abort a scan on bytes already transferred, so the rows are returned — they have been paid for — and the flag tells you the query was too expensive and the next one should be narrower. The row ceiling, by contrast, is enforced.

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription
source_catalogEvery remote GeoParquet dataset this server can query, with licence, current release path, geometry and category columns, and approximate size. Read this first: it is the list of names every tool accepts.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rteina/geoparquet-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server