Tool reference
Arguments, filter operators, response shapes, and worked examples for all eight parcel MCP tools.
Every tool returns a single text content block containing JSON. The examples below show the arguments object you (or the model) pass to tools/call, and the parsed JSON that comes back.
Two conventions apply everywhere:
parcel_schemafirst. It is cheap, and it is how a model learns thatstatefpis a two-digit FIPS string and thatusedesc— notusecode— holds the human-readable land use. Filters written without it tend to invent column names.- Geometry is never returned in rows. The
geomcolumn is excluded from every row response, even if you ask for it, because raw WKT wrecks a model's context window. Usecentroidx/centroidyfor a point, or the spatial tools (parcel_near_point,parcel_at_point,parcel_within_bbox,parcel_within_polygon) to search by shape. The one exception isparcel_at_point, which returns the polygon on request viainclude_geometry. If you need polygon geometry in bulk, use the WFS endpoint directly. - Centroids, not boundaries.
parcel_near_point,parcel_within_bboxandparcel_within_polygonall test the parcel centroid, so a parcel straddling the edge of your shape is included only when its centroid is.parcel_at_pointis the exception: it tests the real polygon.
Shared arguments
| Argument | Applies to | Default | Notes |
|---|---|---|---|
limit |
every list-returning tool | 10 |
Clamped to 1,000 no matter what is requested |
offset |
every list-returning tool | 0 |
For pagination; must be a non-negative integer |
columns |
every row-returning tool | all columns except geom |
Validated against the published schema; lrid is always included so results can be correlated |
Passing an unknown column name, a non-integer limit, or a negative offset returns a clean error rather than a malformed query — see error shapes.
Filter operators
parcel_query and parcel_count take a where array of {column, op, value} conditions. All conditions are AND-ed together — there is no OR, and no nesting. Use in for set membership, or run two calls and combine the results.
op |
Meaning | value type |
|---|---|---|
eq |
equals | string, number, or boolean |
ne |
not equals | string, number, or boolean |
gt ge |
greater than / greater or equal | number (or string) |
lt le |
less than / less or equal | number (or string) |
like |
case-sensitive pattern (% wildcard) |
string |
ilike |
case-insensitive pattern (% wildcard) |
string |
in |
member of a list | non-empty array |
is_null |
null test | true for IS NULL, false for IS NOT NULL |
Values are type-checked and escaped before they reach the query layer, and column names are checked against the allow-list, so an invented column or a quote inside a string fails cleanly instead of producing a broken filter.
parcel_schema
Lists every queryable column with its type and description. No arguments.
{}Response:
{
"count": 53,
"columns": [
{ "name": "lrid", "type": "uuid", "description": "Unique parcel identifier (UUID). Use this for parcel-by-id lookups." },
{ "name": "parcelid", "type": "string", "description": "Parcel ID assigned by the county assessor." },
"…"
]
}The full column set, as advertised to the model:
| Column | Type | Description |
|---|---|---|
lrid |
uuid | Unique parcel identifier — use with parcel_by_id |
parcelid |
string | Parcel ID assigned by the county assessor |
parcelid2 |
string | Alternate parcel ID |
ogparcelid |
string | Original parcel ID from the source record |
geoid |
string | Census GEOID (state + county + tract + block) |
statefp |
string | Two-digit state FIPS code (e.g. 06 for California) |
countyfp |
string | Three-digit county FIPS code |
countyname |
string | County name (e.g. Alameda) |
taxacctnum |
string | Tax account number |
taxyear |
integer | Tax year the assessment applies to |
usecode |
string | Land use code |
usedesc |
string | Land use description (e.g. Single Family Residential) |
zoningcode |
string | Zoning code |
zoningdesc |
string | Zoning description |
numbldgs |
integer | Number of buildings on the parcel |
numunits |
integer | Number of units (multi-family) |
yearbuilt |
integer | Year the primary structure was built |
numfloors |
float | Number of floors in the primary structure |
bldgsqft |
float | Total building square footage |
bedrooms |
float | Number of bedrooms |
halfbaths |
integer | Number of half bathrooms |
fullbaths |
integer | Number of full bathrooms |
imprvalue |
number | Improvement value (USD) |
landvalue |
number | Land value (USD) |
agvalue |
number | Agricultural use value (USD) |
totalvalue |
number | Total assessed value (USD) |
taxacres |
float | Tax acres |
saleamt |
number | Most recent sale amount (USD) |
ownername |
string | Owner name — indexed as full-text tokens |
ownertype |
string | Owner classification (government, corporate, …). Low cardinality; indexed |
owneraddr |
string | Owner mailing address |
ownercity |
string | Owner mailing city |
ownerstate |
string | Owner mailing state |
ownerzip |
string | Owner mailing ZIP |
parceladdr |
string | Parcel site address (street) |
parcelcity |
string | Parcel site city |
parcelstate |
string | Parcel site state (two-letter) |
parcelzip |
string | Parcel site ZIP |
accesstype |
string | Access classification. Mostly null; indexed for the non-null rows |
placetype |
string | Place classification. Mostly null; indexed for the non-null rows |
legaldesc |
string | Legal description (PLSS reference) |
township |
string | PLSS township |
section |
string | PLSS section |
qtrsection |
string | PLSS quarter section |
range |
string | PLSS range |
plssdesc |
string | PLSS full description |
book |
string | Record book reference |
page |
string | Record page reference |
updated |
datetime | Last update timestamp |
centroidx |
float | Parcel centroid longitude (WGS84) |
centroidy |
float | Parcel centroid latitude (WGS84) |
surfpointx |
float | Parcel surface point longitude (WGS84) |
surfpointy |
float | Parcel surface point latitude (WGS84) |
geom is deliberately not in this list. A column a model can name is a column it can put in columns, and selecting geometry that way returns hex EWKB rather than anything readable — geometry reaches a caller as GeoJSON, from parcel_at_point or the WFS endpoint.
Only lrid, parcelid, parcelid2, ownername, geoid, ownertype, accesstype, placetype and the (centroidx, centroidy) pair are indexed on this copy. statefp and countyfp are not — filter a state or county with a geoid range instead (geoid ge '25021' AND geoid lt '25022'), or an unindexed filter over 158M rows will hit the statement timeout.
Attribute coverage varies by county — see Coverage Statistics for what is populated where, and Data Content for the full field semantics.
parcel_query
Run a structured filter and return matching rows. The geoid range below is Alameda County, CA — it is the indexed condition that makes the rest of the filter answerable.
{
"where": [
{ "column": "geoid", "op": "ge", "value": "06001" },
{ "column": "geoid", "op": "lt", "value": "06002" },
{ "column": "usedesc", "op": "ilike", "value": "%single family%" },
{ "column": "yearbuilt", "op": "gt", "value": 1950 },
{ "column": "totalvalue", "op": "ge", "value": 750000 }
],
"columns": ["parceladdr", "parcelcity", "ownername", "totalvalue", "yearbuilt"],
"limit": 25,
"offset": 0
}Response:
{
"type": "FeatureCollection",
"numberMatched": 4137,
"numberReturned": 25,
"returned": 25,
"features": [
{
"lrid": "6f1c2a94-4b3e-4f2a-9c11-0f7b2d5e8a31",
"parceladdr": "1234 Oak St",
"parcelcity": "Oakland",
"ownername": "DOE JOHN A & JANE B",
"totalvalue": 982000,
"yearbuilt": 1962
},
"…"
]
}features holds flat attribute objects, not GeoJSON features — there is no nested properties or geometry key. numberMatched is the total matching the filter (useful for paging); numberReturned and returned are what came back in this call.
Omit where entirely and you get an unfiltered page of the layer — occasionally useful for a sanity check, rarely what you want.
parcel_count
The same where filter, returning only a number. Prefer this whenever the question is "how many" — it keeps thousands of rows out of the model's context. Counts get a 90-second budget rather than the 30 seconds rows get; if one times out the total is genuinely unknown, so call again (the attempt warms the cache) or narrow the filter rather than estimating. With no filter at all, the answer is the table's planner estimate, flagged as estimated.
{
"where": [
{ "column": "geoid", "op": "ge", "value": "51" },
{ "column": "geoid", "op": "lt", "value": "52" },
{ "column": "usedesc", "op": "ilike", "value": "%agricultur%" },
{ "column": "taxacres", "op": "ge", "value": 100 }
]
}{ "count": 28914 }parcel_by_id
One parcel by its lrid UUID. Non-UUID input is rejected before any query runs.
{ "lrid": "6f1c2a94-4b3e-4f2a-9c11-0f7b2d5e8a31" }Found:
{
"found": true,
"parcel": {
"lrid": "6f1c2a94-4b3e-4f2a-9c11-0f7b2d5e8a31",
"parcelid": "1438267944",
"parceladdr": "1234 Oak St",
"ownername": "DOE JOHN A & JANE B",
"totalvalue": 982000,
"…": "…"
}
}Not found:
{ "found": false, "lrid": "6f1c2a94-4b3e-4f2a-9c11-0f7b2d5e8a31" }parcel_near_point
Parcels whose centroid lies within a distance of a WGS84 point. The query is an indexed range scan over centroidx / centroidy, then a true-metres haversine filter on the centroid — so a large parcel whose centroid sits outside the radius is missed even when the point is inside its boundary. When you want the parcel at a coordinate, use parcel_at_point.
| Argument | Required | Notes |
|---|---|---|
longitude |
yes | WGS84 longitude (x) |
latitude |
yes | WGS84 latitude (y) |
distance_meters |
yes | Must be positive; meters, not feet or degrees |
limit / offset / columns |
no | See shared arguments |
{
"longitude": -76.27836,
"latitude": 36.87886,
"distance_meters": 250,
"limit": 20
}The response is the same FeatureCollection-style envelope as parcel_query. Note the argument order: longitude first, latitude second — the same convention as GeoJSON, and the opposite of how people usually say coordinates aloud.
parcel_at_point
The parcel whose polygon actually contains a WGS84 point — an exact ST_Intersects test against the boundaries, not a proximity guess. This is the right tool for "what parcel is at this location".
| Argument | Required | Notes |
|---|---|---|
longitude |
yes | WGS84 longitude (x) |
latitude |
yes | WGS84 latitude (y) |
limit |
no | Default 1. Raise it only for stacked parcels, which can genuinely overlap |
include_geometry |
no | Default false. true returns the full polygon — large, so ask only when you need it |
{
"longitude": -76.27836,
"latitude": 36.87886
}The response is not the parcel_query envelope; it is a lookup result:
{
"found": true,
"exact": true,
"query": { "longitude": -76.27836, "latitude": 36.87886 },
"parcels": [
{
"type": "Feature",
"id": 7,
"properties": { "lrid": "…", "ownername": "…", "parceladdr": "…" },
"centroid": { "longitude": -76.2784, "latitude": 36.8789 },
"tile": { "z": 16, "x": 18800, "y": 25585 }
}
],
"geometry_included": false
}found: false with an empty parcels array means there is genuinely no parcel at that coordinate — the test is against real geometry, so it is not a coverage artifact of the tile archive.
parcel_within_bbox
Parcels whose centroid falls inside a WGS84 bounding box. This is the viewport-shaped search, and it rides the indexed (centroidx, centroidy) pair.
{
"minx": -122.28,
"miny": 37.80,
"maxx": -122.26,
"maxy": 37.82,
"limit": 100
}minx/maxx are longitudes, miny/maxy latitudes; each min must be less than or equal to its max or the call is rejected. Same response envelope as parcel_query. A parcel larger than the box, or one straddling its edge, is returned only when its centroid falls inside.
parcel_within_polygon
Parcels whose centroid falls inside a GeoJSON Polygon or MultiPolygon — city limits, a service area, a hand-drawn shape. The polygon's bounding box drives the indexed scan; each candidate centroid is then tested against the rings.
{
"geojson": {
"type": "Polygon",
"coordinates": [[
[-122.28, 37.80],
[-122.26, 37.80],
[-122.26, 37.82],
[-122.28, 37.82],
[-122.28, 37.80]
]]
},
"limit": 500
}Rules:
- Only
PolygonandMultiPolygonare accepted. AFeatureorFeatureCollectionwrapper is not — pass the bare geometry object (itsgeometryvalue). - Coordinates are
[longitude, latitude]in WGS84. - Each ring needs at least four positions; an unclosed ring is closed automatically.
Same response envelope as parcel_query. Because corner rows are discarded after the fetch, a large polygon can exhaust one fetch before filling the page; when that happens the response carries a note saying results may be incomplete. For very large or complex shapes, prefer a coarse parcel_within_bbox followed by a filter, or the WFS endpoint — the 1,000-row cap applies here too.