PVAToday for AI
The county’s property record, as tools any AI can call.
McCracken County’s record, live from the PVA’s own exports, served over the Model Context Protocol. Ask Claude, or any assistant that speaks MCP, about a parcel, an owner, a street or a sale, and it looks the answer up instead of guessing, and tells you which export it came from.
Why it matters
The record was always public. Now it can be asked.
Answers, not search boxes
“Who owns the lots around the park?” used to mean a lookup per parcel. An assistant with these tools finds the park, draws the circle, and reads every parcel in it, in one question.
From the source, with a date on it
Everything comes from the exports the PVA office delivers, and every reply says which one. No scraped copies, no mystery databases, nothing merged in from elsewhere.
History, not just today
Ask how a parcel stood on a past date, walk its transfers back to 1979, or see exactly what changed between two deliveries.
Fairness you can check
Valuation context places an assessment against real nearby sales, and the ratio study measures a whole class the way assessors do. Each sale is compared with the value from before it sold, and the method and the limits are stated every time.
Connect
One public endpoint, streamable HTTP. No account needed to read the public record.
The endpoint
Livehttps://mcp.pva.today
Every tool is open to the public. A bearer token issued to a PVA office, or to operations, additionally unlocks looking up everything behind one mailing address, the QA report on its own deliveries and the raw rows as delivered. Nobody else needs one.
In Claude
- Open Settings, then Connectors, and choose Add custom connector.
- Name it PVA Today and paste the endpoint URL.
- Turn it on in a chat and ask about McCracken County.
In Claude Code:
claude mcp add --transport http pva-today https://mcp.pva.today
Any MCP client
Clients that take a JSON config with remote servers:
{
"mcpServers": {
"pva-today": { "type": "http", "url": "https://mcp.pva.today" }
}
}
Clients that only launch local (stdio) servers can bridge to it:
npx mcp-remote https://mcp.pva.today
The server also carries its own long-form docs as MCP resources: pva://docs/types for the shared shapes and pva://docs/tools/{name} per tool. A good assistant reads them when a question needs the fine print.
Honest by design
Built so an assistant can’t quietly get it wrong.
Every reply is dated
as_of names the export the answer came from: its date, the roll, the drop, and whether the answer is exact or reconstructed. Where it comes from a single delivered file, it carries that file’s hash.
And sourced
provenance names the family each part of the answer came from: the roll, the sales record, the change log, the delivered rows, or the place table.
A miss says why
found: false comes with a code (NOT_FOUND, AMBIGUOUS, INVALID_SCOPE, STALE_AS_OF, FORBIDDEN_SCOPE) and a reason. An ambiguous address returns the candidates. Nothing is filled in with a guess.
Valuation shows its work
How many sales, from when to when, by what method, and what it can’t see: condition, age, location, time. Each sale is measured against the value from before it sold, not the value the PVA reset to the price. A position against comparables, never an appraisal.
Three clocks, kept apart
When the PVA cut the file, which tax roll a value belongs to, and when a sale or a building happened are three different dates. The tools never blur them.
A record, not a mailing list
Public replies carry the record the PVA publishes, the owner’s mailing address included: it is public record, the same as at the office counter. What needs a credential is the rollup: turning one mailing address into every parcel behind it. A single record is an answer; a list keyed off an address is a different thing.
Warnings travel with the answer
Anything the model should say out loud (a truncated list, a filter not applied, a field that isn’t shown to this caller) rides along in warnings.
What every reply looks like
The same envelope from every tool. The reply to get_data_status, trimmed:
{
"county": "ky/mccracken",
"as_of": {
"requested": "latest",
"served_from": "drop:mccracken-2026-09-23",
"drop_id": "mccracken-2026-09-23",
"export_date": "2026-09-23",
"roll": "2027 working roll",
"fidelity": "exact"
},
"found": true,
"data": { "export_date": "2026-09-23", "cadence_days": 210, … },
"provenance": { "parcels": "pva_roll" },
"warnings": []
}
county- Which county answered (ky/mccracken).
as_of- What was asked for, what served it: drop, export date, roll, fidelity (exact, reconstructed or unknown), and the delivered file and its hash when one file answers.
found- true for an answer. false is a miss, never an error or a guess.
code, reason- On a miss: why, in a word and a sentence.
candidates- On AMBIGUOUS: the parcels it could mean, so the next call can name one.
data- The answer itself.
provenance- Where each family of data came from.
warnings- What the answer should mention.
cursor, truncated- On paged lists: how to get the next page, and whether a limit cut this one.
Shared shapes
Most tools name one parcel (a Locator) or an area (a Scope). The fields sit flat among the tool’s other arguments.
Locator: one parcel
Give exactly one.
| Argument | Type | What it does |
|---|
parcel_id | string | A parcel number, in the PVA's form (for example 123-45-67-890). |
address | string | A street address. A leading house number anchors the match, so 315 BROADWAY never returns 2315 BROADWAY. Several matches come back as AMBIGUOUS with up to five candidates. |
lat + lng | number | A point; the parcel that contains it. |
Scope: an area
Give at most one.
| Argument | Type | What it does |
|---|
district | string | A tax district code (02) or a taxing district name. |
street | string | A street name, e.g. BROADWAY. |
lat + lng + radius_meters | number | A circle. radius_meters defaults to 500, max 5000. |
west, south, east, north | number | A bounding box. |
(nothing) | | The whole county. |
as_of: a moment
An ISO date or a drop id. On or after the first export held, it resolves to the newest export on or before that date. Before it, values can’t be known and the reply is STALE_AS_OF; ownership before then is in get_timeline’s transfers.
Find a place or a parcel
Start here: turn a name, an address or a point on the map into the county's record.
Turns a place, landmark, road or neighborhood name into coordinates, so the area tools have somewhere to look. Fuzzy matching over the county's place table: neighborhoods, roads, schools, parks, landmarks.
| Argument | Type | What it does |
|---|
query | string, required | A place, landmark, road or neighborhood name. |
category | string | Filter, e.g. school, park, neighborhood, road. |
limit | integer | Default 5, max 25. |
Places best first, each with name, coordinates, category and a match score. Use the coordinates as a scope for the area tools.
Ask a chatWhat's near the National Quilt Museum?
Every named place in the county, with the parcel each one sits on and its owner of record. The list behind pva.today/mccracken/places.
Access. Open to everyone. Private homes (house, residential, apartment) are never listed, and the owner is a name only, never mailing detail.
| Argument | Type | What it does |
|---|
kinds | string | places (default) | all. all adds roads, towers, pipelines, parking and the like, each flagged infrastructure. |
category | string | Only this category, e.g. park, school. |
query | string | A name fragment. |
limit | integer | Default 200, max 1000. |
offset | integer | Where the page starts: the previous page's next_offset. |
cursor | string | From the previous page's reply, to get the next page. |
places, each with id, name, category, lat, lng, infrastructure, and parcel (parcel_id, site_address, owner) or null when the place is on no parcel. categories counts each category over the kinds asked for, before the category and query filters. total, returned and next_offset (null on the last page). hidden counts what was left out: infrastructure, and private_homes.
Ask a chatWhat parks are in McCracken, and who owns each?
Finds parcels by address fragment or parcel-number prefix, by area, or by filters. Largest first unless you ask for another order.
| Argument | Type | What it does |
|---|
query | string | Parcel-number prefix or address fragment, at least 2 characters. A leading house number anchors. |
scope | Scope | At most one of district, street, a circle or a box; nothing means the county. See Shared shapes. |
owner_type | string | individual | llc | trust | estate | corporate | government | religious |
absentee_only | boolean | Only parcels whose mailing address differs from the site address. |
record_status | string | assigned (default) | unassigned (map-only parcels) | any |
built_since | integer | Only parcels with a building built in or after this year. |
sort | string | size (default, largest first) | value (highest assessed first) | distance (the default with lat + lng) | parcel_id |
detail | string | summary (default: address and owner per hit) or ids (parcel ids, for a map). |
limit | integer | Default 20; max 25 at summary, 500 at ids. |
cursor | string | From the previous page's reply, to get the next page. |
Current records only. Each hit carries acreage, assessed value and class. Paged: cursor for the next page, truncated when a limit cut the list.
Ask a chatWhat are the largest parcels the government owns along the riverfront downtown?
One parcel, whole: address, owner, the assessment for the roll in force, deed, last transfer and buildings. Ask for a past date and it answers as the record stood then.
Access. Open to everyone. At full detail the reply includes the owner's mailing address, which is public record; summary leaves it out.
| Argument | Type | What it does |
|---|
locator | Locator | Exactly one of parcel_id, address, or lat + lng. See Shared shapes. |
detail | string | summary (default) trims the legal description and mailing detail; full keeps them. |
include_buildings | boolean | Default true. |
include_geometry | boolean | Inline GeoJSON. Default false; geom_uri is always present. |
as_of | string | An ISO date or a drop id: the parcel, owner and assessment as they stood then. Default the latest export. Buildings have no history and are always the latest. |
The parcel record, with the delivered file it came from and that file's hash in as_of.
Ask a chatWho owned the McCracken County Courthouse parcel in February, and what was it assessed at?
The buildings on one parcel or across an area: type, year built, size, condition, grade and value.
| Argument | Type | What it does |
|---|
parcel_id / address | string | One parcel (give one), or leave both out and give a scope. |
scope | Scope | At most one of district, street, a circle or a box; nothing means the county. See Shared shapes. |
built_since | integer | Only buildings built in or after this year. |
limit | integer | Default 50, max 200. |
cursor | string | From the previous page's reply, to get the next page. |
Buildings newest first. They come from the improvements file and have no history.
Ask a chatWhat's been built within a mile of Kentucky Oaks Mall since 2020?
Back to the tool list
Sales and history
What sold, what changed, and a parcel's story back to 1979.
Back to the tool list
Value and fairness
How assessments compare with what property sells for, stated with its method and its limits.
Every sale is measured against the value from before it sold
When a parcel sells, the PVA resets its assessed value to the sale price. So most recent sales match their current assessment exactly, and a ratio of today's value to the sale price would measure nothing: it would call every roll perfect.
Both tools measure each sale against the value the parcel carried before the sale: the roll last delivered before the sale date, as it was first delivered. That means only sales after the first roll PVAToday holds (February 2026) can count, and the window of usable sales grows with each roll the office delivers. Early on, samples are small, and the replies say so.
Back to the tool list
The record and its deliveries
How current the data is, where each fact came from, and what the office sent.
Which PVA export the data reflects, and a one-line pulse. A good first call: it tells you how current everything else is.
No arguments.
The export date and roll, the five pipeline stages with times (received, verified, loaded, diffed, published), the export before it, the cadence in days, change counts since the export before, and a pulse line (sales per month, median arms-length price).
Ask a chatHow current is the McCracken County data?
Which export first carried a parcel's current owner, value, deed, address, class, exemption or zoning, with a pointer to the exact delivered row.
| Argument | Type | What it does |
|---|
locator | Locator | Exactly one of parcel_id, address, or lat + lng. See Shared shapes. |
field | string | owner | value | deed | address | class | exemption | zoning. Default every field with a recorded change. |
For each field, the most recent recorded change that set the current value: the drop, its export date, and a raw_uri to the delivered row with the file's hash, so the record can be cited to the byte. Unchanged since the first export means the origin is genesis.
Ask a chatWhen did the courthouse parcel's current owner first show up in the PVA's exports?
The Register: one delivery (drop) or a window of them, with stages, the files sent and change counts.
Access. Open to everyone. The QA report and notes are shown to the office (its own drops) and ops.
| Argument | Type | What it does |
|---|
drop_id | string | One drop, e.g. mccracken-2026-09-23. |
delivery_id | string | One upload's id from the ingestion service. |
date | string | ISO date: the drop exported or received that day. |
from + to | string | List drops in the window (export date). |
stage | string | Only drops that reached this stage: received | verified | loaded | diffed | published. |
limit | integer | Default 20, max 100. |
cursor | string | From the previous page's reply, to get the next page. |
Newest first. Each entry: source, roll, the five stage times, the files sent with hashes, the previous drop, change counts by kind.
Ask a chatList every delivery from the McCracken PVA this year and where each one stands.
The rows of one delivery: the changes it made, or (for the office and ops) the rows exactly as delivered.
Access. view changes is open. view raw is for the office (its own drops) and ops; anyone else gets FORBIDDEN_SCOPE.
| Argument | Type | What it does |
|---|
drop_id | string, required | The drop. |
view | string | changes (default) | raw (rows as delivered). |
unit | string | For raw: which file, e.g. tblREProperty.txt. |
kind | string | For changes: one change kind. |
parcel_id | string | One parcel's rows (across files, for raw). |
limit | integer | Default 50, max 500. |
cursor | string | From the previous page's reply, to get the next page. |
Change rows, or raw rows with fields in delivered order, each with a raw_uri and the file's hash.
Ask a chatShow me every transfer in the September delivery.
What the office should check in a delivery: the things that look wrong, counted, with examples.
| Argument | Type | What it does |
|---|
drop_id | string | Default the latest drop. |
kind | string | future_date | junk_price | future_building | undated_building | big_restatement | contract_unit_left_out | retired | qa. Default all. |
limit | integer | Examples per kind. Default 5, max 50. |
Counts per kind with examples: future-dated sales (with the years the rest of that deed book carries), prices that look like account numbers, buildings dated after the export or undated, prior-year values restated by 25% or more, contract files left out, parcels gone from both the table and the map, and what verification flagged in the files.
Ask a chatAnything in the latest export the PVA office should double-check?
Back to the tool list