# 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 ```mermaid 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](https://www.huduser.gov/portal/dataset/uspszip-api.html), 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](https://www.huduser.gov/hudapi/public/usps). 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`): ```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.