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_schema first. It is cheap, and it is how a model learns that statefp is a two-digit FIPS string and that usedesc — not usecode — holds the human-readable land use. Filters written without it tend to invent column names.
  • Geometry is never returned in rows. The geom column is excluded from every row response, even if you ask for it, because raw WKT wrecks a model's context window. Use centroidx / centroidy for 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 is parcel_at_point, which returns the polygon on request via include_geometry. If you need polygon geometry in bulk, use the WFS endpoint directly.
  • Centroids, not boundaries. parcel_near_point, parcel_within_bbox and parcel_within_polygon all test the parcel centroid, so a parcel straddling the edge of your shape is included only when its centroid is. parcel_at_point is 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 Polygon and MultiPolygon are accepted. A Feature or FeatureCollection wrapper is not — pass the bare geometry object (its geometry value).
  • 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.

On this page

Tool reference | Land Records