Limits and safety

Row caps, the schema allow-list, plan gating and metering, error shapes, data freshness, and what the MCP server deliberately cannot do.

Giving a language model direct access to a production database is a reasonable thing to be nervous about. The server is narrow by design.

What it cannot do

  • Read-only. Every tool issues a select against the parcel layer. There is no tool that writes, updates, or deletes anything, and no tool that executes arbitrary SQL or CQL.
  • No free-text query language. Filters arrive as structured {column, op, value} objects, validated field by field, then translated server-side. The model never composes a query string, so there is no injection surface to escape.
  • No other tables. The tools reach exactly one layer — the nationwide parcel layer. Nothing else in the database is exposed.

Row and response caps

Limit Value
Default rows per call 10
Maximum rows per call 1,000 (a larger limit is silently clamped, not rejected)
Geometry in responses Never returned by the row tools — geom is not in the published schema, so it cannot be named in columns. parcel_at_point returns the polygon on request via include_geometry
Columns per row 53 by default; use columns to narrow
Row query timeout 30 seconds (statement_timeout, enforced server-side)
Count query timeout 90 seconds — a timed-out parcel_count means the total is unknown, never an estimate

The row cap protects both the serving layer and the model's context window. For anything that wants more than 1,000 rows at a time, use the bulk downloads or the WFS endpoint with paging — MCP is built for questions, not extraction.

Schema allow-list

Column names in where and columns are checked against the published schema before a query is built, and values are type-checked and escaped. An invented column name fails cleanly with a message telling the caller to call parcel_schema — it never becomes a malformed query. lrid is forced into any explicit columns list so rows can always be correlated back to a parcel.

Plan gating and metering

Access is checked against your plan on every request, before any tool runs, and tool calls are counted against your MCP allowance.

  • A token whose plan has no MCP allowance gets 403 Your plan does not include this feature. — checked on each request, so it happens at the handshake rather than on the first query.
  • Tool calls are metered under the MCP tool calls meter. Your remaining allowance appears alongside your other quotas on your account page, and you'll get the same approaching-limit and over-limit notifications as the tile and record meters.
  • One tool call is one metered request — the protocol overhead is free. initialize, tools/list, ping and notifications cost nothing, so a client that reconnects often is not charged for it. A single conversational question often spans several real calls, though: parcel_schema, then parcel_count, then parcel_query is three. Prompt guidance that avoids needless calls (see Examples) is the practical way to keep usage down.
  • A tool that runs and comes back with an error still counts — it spent a query budget on your behalf. Worth knowing if you are testing an agent in a loop. A call naming a tool that does not exist is refused before anything runs and costs nothing.

Error shapes

Tool errors do not break the JSON-RPC envelope. The call returns normally with a JSON error object in the text block, so the model can read it and adjust:

{
  "error": "CQLBuildError",
  "message": "unknown column: 'owner_name'. Call parcel_schema to see valid columns.",
  "duration_ms": 3
}

Common cases:

error / message Cause
unknown column: '…' Column not in the schema — call parcel_schema
unsupported operator '…' Only eq, ne, gt, ge, lt, le, like, ilike, in, is_null are allowed
in requires a non-empty list in was given a scalar or an empty array
use is_null op instead of a bare None value A null value was passed to a comparison operator
lrid must be a UUID, got … parcel_by_id received something other than a UUID
bbox min must be <= max on each axis minx/miny exceeded maxx/maxy
distance_meters must be positive Zero or negative radius
unsupported geometry type '…' Only Polygon and MultiPolygon — pass the bare geometry, not a Feature
polygon ring must have at least 4 points (closed) Degenerate ring in the supplied GeoJSON

Transport-level problems (bad or missing token, plan not entitled) are HTTP errors instead, and never reach the tools — see the troubleshooting table.

Data freshness

The MCP server reads the same serving layer as the rest of api.landrecords.us, so it inherits the weekly refresh. Ownership transfers, new assessments, and newly ingested county boundaries reach your assistant on the same cycle they reach the tile and WFS endpoints — there is nothing to re-sync on your end.

Attribute coverage still varies by county: a column that exists in the schema may be null for a given parcel because the source assessor doesn't publish it. See Coverage Statistics before concluding that a filter returned nothing because nothing matched.

Operational notes

  • Health check. GET https://api.landrecords.us/mcp/health is unauthenticated and tells you the server is up. It does not validate your token or plan.
  • Transports. Streamable HTTP at https://api.landrecords.us/mcp is the only transport: POST carries JSON-RPC, and GET/DELETE answer 405 because the server is stateless. The legacy SSE pair (/mcp/sse + /mcp/messages/) has been retired and answers 410 with a pointer to the Streamable HTTP endpoint — it needed sticky routing to a single process, which the edge runtime cannot provide.
  • Token handling. The MCP token is a full-access read credential for your plan's quota. Keep it in an environment variable or secret store, not in a checked-in config file, and revoke it from your account page if it leaks.
  • Support. Questions, a callback port you need registered, or a tool you wish existed: hello@landrecords.us. We are actively expanding the tool set.

On this page