ZIP-County Crosswalk MCP
ZIP-County Crosswalk MCP - Look up ZIP-to-county relationships using HUD's official crosswalk data, including population-overlap filtering for ZIPs that cross county lines.
Documentation
ZIP↔County Crosswalk MCP Server
A Model Context Protocol (MCP) server that lets Claude look up ZIP-to-county and county-to-ZIP relationships, using HUD's official USPS ZIP Code Crosswalk API — including residential-address overlap filtering, since ZIP codes routinely cross county lines.
Architecture
flowchart LR
Claude -->|MCP tool call| Server[zip-county-mcp server]
Server -->|overlap ratios| HUD[HUD USPS Crosswalk API]
Server -->|county names| BQ[(BigQuery:\ngeo_us_boundaries)]
Why this exists
ZIP codes and counties don't line up cleanly — a single ZIP can spread
across several counties, each holding a different share of that ZIP's
residential addresses. Most simple ZIP↔county lookups ignore this and just
return one answer, which is often wrong for the county holding a small
sliver of the ZIP. This server exposes HUD's real overlap-ratio data so a
caller can filter out negligible slivers via a min_overlap_pct threshold,
and get accurate county names via a BigQuery public dataset.
Example: ZIP 77494 (Katy, TX) is actually split across three counties —
Fort Bend (83.4%), Harris (16.5%), and Waller (0.17%). A caller who only
wants counties that meaningfully make up that ZIP can set
min_overlap_pct=5 and get back just Fort Bend and Harris.
How overlap is actually measured
The overlap percentage comes straight from HUD's res_ratio field, and
it's worth being precise about what that field is (verified against
HUD's own API docs,
not assumed):
- It's a ratio of residential addresses, not population/headcount. A single-person address and a five-person address both count as "1" toward the ratio — addresses are a reasonable proxy for population, but not the same measurement.
- The denominator flips with query direction. For
zip_to_county(HUDtype=2),res_ratiois addresses-in-this-county ÷ addresses-in-the-whole-ZIP. Forzips_in_county(HUDtype=7, the reverse lookup), it's addresses-in-this-ZIP ÷ addresses-in-the-whole- county. Same field name, different denominator — which is why a populous county like Harris shows dozens of ZIPs at only 1-3% each, while a single ZIP can show one county at 80%+: the two percentages aren't measuring against the same total.
Status
All three tools are implemented, tested (7 passing tests, pytest), and
verified end-to-end against live HUD + BigQuery data and a real Claude
Desktop connection.
Setup
- Get a free HUD API account and Bearer token at huduser.gov.
- Confirm you have BigQuery access to
bigquery-public-data.geo_us_boundaries(e.g. viagcloud auth application-default login). - Copy
.env.exampleto.envand fill inHUD_API_TOKENand your Google Cloud project. - Create a virtualenv with Python 3.10+ (the
mcppackage requires it — on macOS the systempython3is often older, so checkpython3 --versionfirst) and install dependencies:python3 -m venv .venv .venv/bin/pip install -r requirements.txt - Run it:
.venv/bin/python3 server.py
Connecting to Claude Desktop
Add an entry to your claude_desktop_config.json (macOS:
~/Library/Application Support/Claude/claude_desktop_config.json):
"mcpServers": {
"zip-county-mcp": {
"command": "/absolute/path/to/zip-county-mcp/.venv/bin/python3",
"args": ["/absolute/path/to/zip-county-mcp/server.py"]
}
}
Fully quit and reopen Claude Desktop (MCP servers only load at startup), then try one of the questions below.
Example usage
Once connected, just ask Claude in plain English — it picks the right tool and arguments on its own. A few real examples (verified against live data):
Look up a single ZIP:
"What county is ZIP 77002 in?"
Claude calls zip_to_county("77002") → ZIP 77002 (Houston, TX) is entirely
in Harris County, TX (FIPS 48201) — 100% overlap.
A ZIP that crosses county lines:
"What counties does ZIP 77494 overlap, and by how much?"
Claude calls zip_to_county("77494") → three counties: Fort Bend (83.4%),
Harris (16.5%), Waller (0.17%). Ask a follow-up like "only ones with at
least 5%" and it re-calls with min_overlap_pct=5, dropping Waller's
negligible sliver.
Reverse lookup — ZIPs inside a county:
"What ZIP codes are in Harris County, Texas?" (or give it the FIPS code, 48201, directly)
Claude calls zips_in_county("48201") → a list of every ZIP holding a
meaningful share of Harris County's residential addresses, sorted by
overlap.
A list of ZIP codes at once:
"What counties are ZIP codes 77002, 77494, and 10001 each in?"
Claude calls batch_zip_to_county(["77002", "77494", "10001"]) → one
result per ZIP in a single response, without querying BigQuery once per
ZIP behind the scenes.
Tools (v1 scope)
zip_to_county(zip_code, min_overlap_pct=0)— county/counties for a ZIP, with % overlapzips_in_county(county_fips, min_overlap_pct=0)— ZIP codes in a county, with % overlapbatch_zip_to_county(zip_codes, min_overlap_pct=0)— same as above, multiple ZIPs at once
Out of scope for v1: census tract-level lookups, CBSA/congressional district crosswalks, caching layer, other geography types.
Testing
.venv/bin/pip install -r requirements.txt
.venv/bin/python3 -m pytest tests/ -v
Tests mock HUD's API responses (httpx.MockTransport) and the BigQuery
name lookup, so they run in about a second with no live token or BigQuery
access required.