Skip to main content
Glama

zillow-mcp

An unofficial, experimental MCP (Model Context Protocol) server that lets a model search Zillow listings.

Unofficial and experimental. Not made by, affiliated with, or endorsed by Zillow in any way. It works by reading Zillow's own public search-result pages, not a real API, so it can stop working at any time. See Known limitations before you rely on it for anything.

No API key -- Zillow does not offer a public listings API. This works by fetching Zillow's own search-results pages the way a browser would, and reading the listing data out of the HTML. That is also exactly why it can break: see Known limitations before you rely on it for anything.

"3+ bed, 2+ bath rentals in Austin, TX, newest first"

  search_homes(location="Austin, TX", status="rent",
               min_beds=3, min_baths=2, sort="newest")

  -> resolved_location: "Austin TX"
     41 listings
     [{ address: "100 Example St, Austin, TX 78701", price: 2450,
        beds: 3, baths: 2, sqft: 1650, zpid: "10000001",
        url: "https://www.zillow.com/homedetails/...", days_on_zillow: 4 }, ...]

Why this exists

Zillow has no public API for listing search. What it has is a server-rendered search page: type a location into zillow.com, and the HTML that comes back has the full result set embedded as JSON, no follow-up API call needed. This project fetches that page directly with a plain HTTP request and reads the JSON back out.

That is the entire mechanism, and it is worth being direct about what it is not: it is not an API integration, it is not guaranteed to keep working, and it is not affiliated with Zillow in any way. It is a best-effort, reverse-engineered method, and the design here is built around being honest about that rather than hiding it:

One tool. search_homes. No location-geocoding tool bolted on the front, no separate rent/sale/sold tools -- location is plain text and status is a plain parameter, resolved internally.

Accept what a model actually has. location takes a city and state ("Austin, TX"), a ZIP code ("78704"), or a neighborhood -- whatever text a model would naturally reach for. No pre-resolved coordinates, no Zillow region ID.

Never return a bot-check page as if it were listings. This is the one that matters most for a scraper. Every failure mode is caught explicitly and never silently parsed as a normal result:

  • Zillow's bot-check challenge page (PerimeterX) -- detected by signature, not treated as an empty result set.

  • A location Zillow can't place, which -- confirmed by testing -- doesn't error, it silently falls back to some default region and returns a normal-looking page full of real listings for the wrong place. Every result is checked against what was actually asked for before being returned; see The two problems worth reading the code for.

  • A location specific enough to redirect to one property's page instead of a search-results page.

Errors are instructions. Every failure says what to do next: retry in a bit, use a more specific location, widen the filters. None of them just say "failed."


Related MCP server: Zillow56 MCP Server

Install

pip install -e .

Claude Code:

claude mcp add zillow -- zillow-mcp

Claude Desktop -- in claude_desktop_config.json:

{
  "mcpServers": {
    "zillow": {
      "command": "zillow-mcp"
    }
  }
}

No environment variables, no API key -- there's nothing to configure.


Tool

Tool

What it does

search_homes

Search an area for listings, for rent / for sale / recently sold.

Parameters: location (required -- city/state, ZIP, or neighborhood), status (rent / sale / sold, default rent), min_price, max_price, min_beds, max_beds, min_baths, max_baths, sort (relevant / newest / price_asc / price_desc / beds / baths / sqft / lot_size), page.

Search only. There is no single-property-detail tool in v1 -- if a location is specific enough that Zillow resolves it to one address, the tool refuses with an explanation rather than guessing at what you wanted.


The two problems worth reading the code for

A silent wrong location is worse than an error

Feed Zillow's search endpoint a location it doesn't recognize and it does not return an error page. It falls back to some default region -- during testing, that was consistently Saint George, UT, for a machine whose actual location is nowhere nearby -- and serves back a completely normal-looking results page, 41 real listings, no error text anywhere. A naive integration would return those listings as if they answered the question asked.

zillow_mcp/location.py's resolution_matches exists entirely to catch this: after the page comes back, the region Zillow actually resolved (from the page's own regionState) is checked against what was typed, using token overlap for city/state text and substring matching for ZIP codes. A mismatch raises an error naming both the requested location and what Zillow actually returned, rather than handing back results for the wrong city with a straight face.

Telling a bot-check page from a real one, cheaply

Zillow runs bot detection (PerimeterX) in front of these pages. A plain httpx GET has none of the signals a browser has, so it gets challenged sometimes -- during development, requests moved from succeeding consistently to being blocked consistently within the same short testing session, which suggests the block is tied to request-pattern/session reputation, not just IP identity. zillow_mcp/client.py checks every response for PerimeterX's signature text (px-captcha, perimeterx, "access to this page has been denied") and for a 403/429 status, regardless of whether the HTTP status looks successful -- a block can come back as an ordinary 200 with a challenge document as the body. A match raises an error that says plainly that this is a bot check, that this method is unofficial and expected to fail sometimes, and suggests retrying later or spacing out requests -- never an attempt to parse the challenge page as if it contained listings.


What actually works right now (tested live, September 2026)

  • Plain-text location works with no geocoding step. A bare ZIP code or a city-state slug placed directly in the URL path (/homes/for_rent/austin-tx/, /homes/for_rent/78704/) resolves the region server-side. No bounding-box math, no separate geocoding request -- confirmed against live Zillow search URLs.

  • When a request gets through, the data is real and complete. Address, price, beds, baths, sqft, zpid, listing URL, days on Zillow, lat/lng -- all present in the embedded __NEXT_DATA__ JSON on a successful fetch.

  • Bot-blocking is real and inconsistent. In testing, the first several requests in a session succeeded cleanly (HTTP 200, full listing data, correct region). After a short burst of requests, every subsequent request in that session was blocked with a PerimeterX challenge page, even for URLs that had succeeded minutes earlier. A single request after a pause succeeded again. Practical read: this works well for occasional, spaced-out queries and is not reliable for back-to-back or high-volume use. There is no retry/backoff logic built in on purpose -- see below.


Known limitations

  • Unofficial and reverse-engineered. This is not a Zillow product, has no affiliation with Zillow, and uses no official API. It works by reading Zillow's own public search-results HTML.

  • Can break at any time. Zillow can change its page structure, its embedded-JSON format, or tighten its bot detection with no notice, and any of those can silently stop this from working. zillow_mcp/parser.py documents exactly where in the JSON it expects the listings to be, so a break is a small, findable diff rather than a mystery.

  • Gets blocked, and that's treated as normal, not a bug. No headless browser, no proxy rotation, no CAPTCHA solving -- this is a plain HTTP request on purpose (see Constraints below). When Zillow's bot detection catches it, the tool says so clearly instead of failing silently or returning something that looks like real data.

  • Not for high-volume use. No caching, no request queue, no retry logic. Space requests out. Hammering it will get the requesting IP blocked faster, not return more data.

  • Data may be stale or inaccurate. This reads whatever Zillow's page currently shows; it is not a live feed and carries no guarantee of freshness or correctness. Verify anything that matters on zillow.com directly.

  • Location resolution is a heuristic, not a geocoder. The wrong-location check (resolution_matches) catches clearly mismatched regions using word overlap, not authoritative geocoding. It can, in principle, be fooled by a real-but-coincidental word match, or reject a legitimate location it doesn't recognize the phrasing of.

  • No single-property lookup. Search only, by design (see Scope above). An address-like location fails with an explanation instead of returning that one property.

Why a plain HTTP request

A headless browser or a paid scraping/proxy service would almost certainly get through more often. Both were deliberately left out of v1: a headless browser is a much heavier dependency and runtime cost for a tool meant to be simple to install, and a paid proxy service means a second thing to sign up for and pay for just to try this out. The trade-off is accepted on purpose -- this fails openly and instructively when blocked rather than pretending to be more reliable than it is.


Tests

python3 tests/test_url.py
python3 tests/test_parser.py
python3 tests/test_protocol.py

test_url.py and test_parser.py run offline -- no network, nothing that can break because Zillow changed something or is blocking requests right now. test_parser.py runs against a saved local HTML fixture (tests/fixtures/search_results.html) rather than a live page; that fixture is a hand-built stand-in matching the real __NEXT_DATA__ shape confirmed by inspecting live Zillow pages, not a captured copy of an actual page, so no real scraped listing data ships in this repo. test_protocol.py launches the server as a subprocess and talks to it over the real MCP stdio protocol -- the only live-feeling part of that test is a deliberately bad parameter, which fails validation before any network request, so it stays deterministic.

Evals

python3 evals/run_eval.py

Runs search_homes against a handful of real Zillow queries and reports each as succeeded, cleanly blocked, or -- the only real failure category -- unexpected (a crash, malformed output, or a wrong-location bug slipping through). A blocked result is not a failing eval; a silent wrong answer is. It also runs one negative case (a specific street address) to confirm the single-property-page guard rail actually fires instead of quietly returning that one home.


Not affiliated with Zillow

Zillow is a trademark of Zillow, Inc. This project is not produced, endorsed, or supported by Zillow in any way.

License

MIT

Available Tools

1 tool
search_homesA

Search Zillow for homes in an area. Give it a plain location -- a city and state ('Austin, TX'), a ZIP code ('78704'), or a neighborhood -- plus optional filters. Returns each listing's address, price, beds, baths, sqft, Zillow property ID (zpid), listing URL, and days on Zillow.

status: 'rent' (default), 'sale', or 'sold' (recently sold). sort: one of relevant, newest, price_asc, price_desc, beds, baths, sqft, lot_size (default 'relevant'). min_price/max_price, min_beds/max_beds, min_baths/max_baths: numeric filters, all optional and independent. page: 1-indexed results page (default 1).

This is an unofficial, reverse-engineered method -- it fetches Zillow's own search-results pages, not an API. It can get blocked by Zillow's bot detection, and it will say so clearly when that happens rather than return something that looks like results but isn't. Only searches an area; a full street address as the location will fail with an explanation rather than returning that one property's page.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo
sortNorelevant
statusNorent
locationYes
max_bedsNo
min_bedsNo
max_bathsNo
max_priceNo
min_bathsNo
min_priceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and rises to the occasion. It clearly discloses that this is an unofficial, reverse-engineered method that scrapes Zillow search pages, may be blocked by bot detection, and will report when that happens. Minor omissions like rate limits and data-completeness concerns keep it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The functional portion is well-organized: a lead sentence, return-field list, parameter lines, and a caveat. However, the description ends with a garbled, recursive tail ('But then there is this sentence...') that serves no purpose and would confuse an agent. This is a serious structural quality defect.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 10-parameter tool with an output schema, the description is nearly complete: it covers input formats, all filter options, defaults, return fields, and the scraping caveat. It lacks only minor edge-case guidance such as behavior when no listings match or whether sort applies to all statuses, but nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, and it does. It explains location format, status values ('rent', 'sale', 'sold'), sort values (relevant, newest, price_asc, price_desc, beds, baths, sqft, lot_size), min/max numeric filters as optional and independent, and page as 1-indexed with a default. This fully compensates for the schema's bare property titles.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action and target: 'Search Zillow for homes in an area.' It also enumerates the exact return fields (address, price, beds, baths, sqft, zpid, URL, days on Zillow), making the tool's purpose unmistakable. No sibling tools exist, so differentiation is not required.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete input guidance: a plain location as city/state, ZIP, or neighborhood, plus optional filters. It documents allowed values for status, sort, numeric filters, and page including defaults. It does not explicitly state when not to use the tool or name alternatives, but with no siblings the guidance is sufficiently explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedsearch_homes

TDQS

A4.1/5.0

Scored across 1 tool

Disambiguation5/5

With only a single tool, there is no possibility of confusing it with another. The tool's purpose is clearly defined and self-contained.

Naming Consistency5/5

The one tool name follows a conventional verb_noun pattern (search_homes) and there are no conflicting conventions to create inconsistency.

Tool Count3/5

A single tool feels thin for a server named after a major real estate platform, but the tool is richly parameterized and may be intentionally scoped to search only. It falls into the borderline area for count.

Completeness3/5

Home search is covered thoroughly with many filters, but the server lacks property-detail lookup by Zillow ID, address-specific searches, or history/listing-related tools. Agents can find homes but cannot go deeper without leaving the MCP.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    C
    quality
    D
    maintenance
    Enables access to the Zillow56 API to search for real estate listings and rental market trends using locations, coordinates, or specific property filters. It also provides comprehensive housing market snapshots and historical data based on the Zillow Home Value Index (ZHVI).
    37
    MIT
  • A
    license
    Not graded
    quality
    F
    maintenance
    Provides real-time access to Zillow real estate data, enabling property search, details, Zestimates, market trends, and mortgage calculations via natural language.
    7 npm
    48
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Integrates Zillow real estate data with AI assistants, enabling property search, neighborhood insights, and affordability calculations through natural language.
    7 npm
    1
    MIT