Parcel MCP Server

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 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_within_bbox, parcel_within_polygon) to search by shape. If you need polygon geometry, use the WFS endpoint directly.

Shared arguments

ArgumentApplies toDefaultNotes
limitevery list-returning tool10Clamped to 1,000 no matter what is requested
offsetevery list-returning tool0For pagination; must be a non-negative integer
columnsevery row-returning toolall columns except geomValidated 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.

opMeaningvalue type
eqequalsstring, number, or boolean
nenot equalsstring, number, or boolean
gt gegreater than / greater or equalnumber (or string)
lt leless than / less or equalnumber (or string)
likecase-sensitive pattern (% wildcard)string
ilikecase-insensitive pattern (% wildcard)string
inmember of a listnon-empty array
is_nullnull testtrue 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": 50,
  "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:

ColumnTypeDescription
lriduuidUnique parcel identifier — use with parcel_by_id
parcelidstringParcel ID assigned by the county assessor
parcelid2stringAlternate parcel ID
ogparcelidstringOriginal parcel ID from the source record
geoidstringCensus GEOID (state + county + tract + block)
statefpstringTwo-digit state FIPS code (e.g. 06 for California)
countyfpstringThree-digit county FIPS code
countynamestringCounty name (e.g. Alameda)
taxacctnumstringTax account number
taxyearintegerTax year the assessment applies to
usecodestringLand use code
usedescstringLand use description (e.g. Single Family Residential)
zoningcodestringZoning code
zoningdescstringZoning description
numbldgsintegerNumber of buildings on the parcel
numunitsintegerNumber of units (multi-family)
yearbuiltintegerYear the primary structure was built
numfloorsfloatNumber of floors in the primary structure
bldgsqftfloatTotal building square footage
bedroomsfloatNumber of bedrooms
halfbathsintegerNumber of half bathrooms
fullbathsintegerNumber of full bathrooms
imprvaluenumberImprovement value (USD)
landvaluenumberLand value (USD)
agvaluenumberAgricultural use value (USD)
totalvaluenumberTotal assessed value (USD)
taxacresfloatTax acres
saleamtnumberMost recent sale amount (USD)
ownernamestringOwner name
owneraddrstringOwner mailing address
ownercitystringOwner mailing city
ownerstatestringOwner mailing state
ownerzipstringOwner mailing ZIP
parceladdrstringParcel site address (street)
parcelcitystringParcel site city
parcelstatestringParcel site state (two-letter)
parcelzipstringParcel site ZIP
legaldescstringLegal description (PLSS reference)
townshipstringPLSS township
sectionstringPLSS section
qtrsectionstringPLSS quarter section
rangestringPLSS range
plssdescstringPLSS full description
bookstringRecord book reference
pagestringRecord page reference
updateddatetimeLast update timestamp
centroidxfloatParcel centroid longitude (WGS84)
centroidyfloatParcel centroid latitude (WGS84)
surfpointxfloatParcel surface point longitude (WGS84)
surfpointyfloatParcel surface point latitude (WGS84)
geomgeometryParcel polygon — advertised but never returned; use the spatial tools or WFS

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.

{
  "where": [
    { "column": "countyname", "op": "eq", "value": "Alameda" },
    { "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.

{
  "where": [
    { "column": "statefp", "op": "eq", "value": "51" },
    { "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_by_address

Geocodes a free-form address, then returns the parcels within a radius of the resolved point. The geocode result comes back alongside the rows so the caller can confirm the match — addresses are frequently ambiguous, and this is what lets an assistant say "I matched 123 Main St in Oakland, CA" before reporting an owner.

ArgumentDefaultNotes
addressrequiredFree-form; include city and state for best results
distance_meters100Search radius around the geocoded point. Increase for rural lots and large parcels
size1How many geocoder candidates to fetch (capped at 5). The top hit is always the one used
limit10Max parcels returned
columnsall but geomSubset of columns
{
  "address": "123 Main St, Oakland, CA",
  "distance_meters": 150,
  "limit": 5,
  "columns": ["parceladdr", "ownername", "totalvalue", "taxacres"]
}
{
  "geocode": {
    "longitude": -122.2711,
    "latitude": 37.8044,
    "label": "123 Main St, Oakland, CA, USA",
    "confidence": 0.96,
    "layer": "address",
    "source": "openaddresses"
  },
  "distance_meters": 150,
  "number_matched": 3,
  "parcels": [
    {
      "lrid": "0a9d7f13-58c2-4e77-b0a6-2c1e94f3d5b8",
      "parceladdr": "123 Main St",
      "ownername": "MAIN STREET HOLDINGS LLC",
      "totalvalue": 1450000,
      "taxacres": 0.18
    },
    "…"
  ]
}

If the geocoder finds nothing, the call returns a clean error naming the address rather than an empty result — retry with more of the address (city, state, ZIP).


parcel_near_point

Parcels within a distance of a WGS84 point, backed by a PostGIS DWITHIN test.

ArgumentRequiredNotes
longitudeyesWGS84 longitude (x)
latitudeyesWGS84 latitude (y)
distance_metersyesMust be positive; meters, not feet or degrees
limit / offset / columnsnoSee 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_within_bbox

Parcels whose geometry intersects a WGS84 bounding box. This is the viewport-shaped search.

{
  "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.


parcel_within_polygon

Parcels intersecting a GeoJSON Polygon or MultiPolygon — city limits, a service area, a hand-drawn shape.

{
  "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. 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