one-roof
# uk-property-mcp
Unified UK housing search as an MCP server. Federates listings across Rightmove,
Zoopla and OnTheMarket, deduplicates the same flat across portals, and joins every
result to UK open government data: the last recorded sale in that postcode (Land
Registry, address-matched where the listing carries a house number), true floor area
and EPC rating, £/sqft, council tax band, flood risk, crime, schools.
Plugs into Claude Desktop / Claude Code (local) or a private, access-locked hosted
instance for ChatGPT and Claude web.
## Status
Feature-complete across sourcing, enrichment, federation, and a hosted UI. Verified
against live UK data this build (25 offline tests pass).
- **Open-data enrichment** — live over keyless UK open data (EPC optional): postcodes.io
geocoding (incl. terminated-postcode fallback), HM Land Registry Price Paid via SPARQL,
data.police.uk crime, Environment Agency flood. Powers `get_property_intelligence`,
`find_comparables`, `market_stats`, `resolve_location` — no scraping required.
- **Rightmove adapter** — typeahead location resolution, `__NEXT_DATA__` search parsing,
`__PAGE_MODEL` detail decoding (a custom numeric-reference format), and **adaptive
price-band tiling** that beats Rightmove's ~1,050-result cap (Clapham reports 1,125 but
only ~1,008 are paginable — tiling recovers the rest).
- **OnTheMarket adapter** + **cross-portal dedup** — the same flat on multiple portals is
merged into one record via geo + beds + price + address signals (an image perceptual-hash
signal plugs in for the hosted tier). Verified live: Leeds search across Rightmove +
OnTheMarket merged 9 cross-portal duplicates.
- **Zoopla adapter** — pluggable transport: a commercial unblocker (hosted, for DataDome)
or a stealth browser (local). Parser validated against a synthetic fixture; field mapping
is re-checked live the first time the unblocker key is set.
- **Hosted web UI** (`public/index.html`) — "One Roof": a search panel, then one card per
home, each carrying the asking price, the portals it was found on, the most recent Land
Registry sale in the same postcode, and how the asking price compares to it. Portal
search rows rarely carry a house number, so that sale is labelled as a nearby comparable
unless the house number matches the deed, in which case the card is marked "this exact
home". Results page 24 at a time and can be sorted by price or by how far below the
nearby sales they sit. One self-contained file, no build step, open or behind a
bearer-token lock depending on `UK_PROPERTY_PUBLIC`. Served by the http entrypoint
alongside `/api/*` and `/mcp`. Design system recorded in `DESIGN.md`; product truth in
`PRODUCT.md`.
- **Hosted deploy** — Dockerfile + `fly.toml`; ChatGPT deep-research `search`/`fetch`
aliases on the MCP endpoint. See `DEPLOY.md`.
### EPC (optional, one env step)
EPC is the only open source needing a free key. Without it everything else still works;
with it you get true floor area and £/sqft. Register at the MHCLG *Get energy performance
of buildings data* service, then set `EPC_API_EMAIL` and `EPC_API_KEY`.
### Build note
In this environment `tsc` runs very slowly; iterate with `npx tsx` (runs the TypeScript
directly) and run `npm run build` when you need `dist/`.
## Deploy the hosted site (one click)
[](https://render.com/deploy?repo=https://github.com/vedzilla/one-roof)
Reads `render.yaml`, builds the Docker image, and gives you a public URL. Set
`UNBLOCKER_API_KEY` (Scrapfly) in the dashboard to turn on Zoopla + OpenRent. See
`DEPLOY.md` for the Fly.io alternative and how to lock it to tokens.
## Add it to Claude (from GitHub)
**Claude Desktop / Claude Code — runs locally, all portals, no hosting.** Clone,
then paste this into your MCP config (Claude Desktop → Settings → Developer → Edit
Config). `npm install` auto-builds via the `prepare` script.
```bash
git clone https://github.com/vedzilla/one-roof && cd one-roof && npm install
```
```json
{
"mcpServers": {
"one-roof": { "command": "node", "args": ["/abs/path/one-roof/dist/entrypoints/stdio.js"] }
}
}
```
Restart Claude, then ask in plain English: *"2-bed flats in Leeds under £270k — what
did they last sell for?"* The open-data tools need no keys.
**Claude.ai web / ChatGPT — needs the hosted server** (see `DEPLOY.md`). Settings →
Connectors → Add custom connector → URL `https://your-host/mcp` (+ `Authorization:
Bearer <token>` if locked).
## Portal coverage
| Portal | Segment | How | Status |
|---|---|---|---|
| Rightmove | sale + rent | `__NEXT_DATA__` + tiling | ✅ verified live |
| OnTheMarket | sale + rent | `__NEXT_DATA__` | ✅ verified live |
| Zoopla | sale + rent | unblocker (hosted) / browser (local) | needs a transport key |
| OpenRent | **rent (direct landlord)** | browser / unblocker (JS-rendered prices) | needs a transport key |
| SpareRoom | **rent (rooms / flatshares)** | direct (static `data-listing-*`) | ✅ parses live |
All dedupe into one record per property. Gumtree and other niche sites are each a
single adapter file implementing `PortalAdapter` — see `src/adapters/`.
## The ten tools
`resolve_location` · `search_listings` · `get_listing` · `get_property_intelligence` ·
`market_stats` · `compare_listings` · `commute_search` · `find_comparables` ·
`track_search` · `list_sources`
## Architecture
One shared core, two run modes (local stdio / hosted HTTP). Open-data enrichment is the
durable spine and is legal to host and share; live listings are fetched politely and,
for Zoopla in hosted mode, via a commercial unblocker. See `NOTICE.md` for the terms and
`src/core/` for the domain model.
## Layout
```
src/core/ domain types, PortalAdapter interface, config
src/mcp/ server builder + the ten tool definitions
src/entrypoints/ stdio (local) and http (hosted) entrypoints
src/adapters/ per-portal adapters (Phase 3+)
src/enrichment/ open-data client (Phase 1–2)
```
TDQS
Scored across 10 tools
Each tool has a clearly distinct purpose: location resolution, listing search, single-listing detail, open-data intelligence, market stats, comparison, commute filtering, comparables, persistent search tracking, and data provenance. Even the two listing-related tools (search_listings and get_listing) are separated by cardinality, and get_listing vs get_property_intelligence are differentiated by data source (live portal vs open data only).
Most tool names follow a consistent verb_noun pattern (resolve_location, search_listings, get_listing, compare_listings, commute_search, find_comparables, track_search, list_sources). The only deviation is 'market_stats', which is a noun phrase rather than a verb-led name, making it slightly inconsistent with the rest.
The 10 tools are well within the ideal 3-15 range and each earns its place by covering a distinct aspect of property search, enrichment, comparison, and trust/provenance. There's no bloat or redundancy.
The surface is nearly complete for a property search server: resolving locations, searching, getting details, open-data intelligence, market stats, comparisons, commute filtering, comparables, and provenance. The only gap is that track_search allows persisting a search but provides no way to list or delete tracked searches, which is a minor dead end that agents can work around.