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 (HUD type=2), res_ratio is addresses-in-this-county ÷ addresses-in-the-whole-ZIP. For zips_in_county (HUD type=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

  1. Get a free HUD API account and Bearer token at huduser.gov.
  2. Confirm you have BigQuery access to bigquery-public-data.geo_us_boundaries (e.g. via gcloud auth application-default login).
  3. Copy .env.example to .env and fill in HUD_API_TOKEN and your Google Cloud project.
  4. Create a virtualenv with Python 3.10+ (the mcp package requires it — on macOS the system python3 is often older, so check python3 --version first) and install dependencies:
    python3 -m venv .venv
    .venv/bin/pip install -r requirements.txt
    
  5. 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 % overlap
  • zips_in_county(county_fips, min_overlap_pct=0) — ZIP codes in a county, with % overlap
  • batch_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.