zillow-mcp
Enables searching Zillow listings for rent, sale, or recently sold properties using a text location (city/state, ZIP, or neighborhood), with filters for price, beds, baths, and sorting options.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@zillow-mcpFind 2-bedroom apartments for rent in Austin under $2000"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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-mcpClaude 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 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-stateslug 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.pydocuments 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.pytest_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.pyRuns 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 toolsearch_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.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| sort | No | relevant | |
| status | No | rent | |
| location | Yes | ||
| max_beds | No | ||
| min_beds | No | ||
| max_baths | No | ||
| max_price | No | ||
| min_baths | No | ||
| min_price | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
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.
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.
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.
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.
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.
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 tool update
v0.1.0- First observed
search_homes
TDQS
Scored across 1 tool
With only a single tool, there is no possibility of confusing it with another. The tool's purpose is clearly defined and self-contained.
The one tool name follows a conventional verb_noun pattern (search_homes) and there are no conflicting conventions to create inconsistency.
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.
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
Related MCP Connectors
Zillow for-sale, for-rent and sold listings, and full property details, as structured JSON.
Zillow real estate listings for sale, rent, and sold, via an Apify Actor, hosted MCP.
Zillow homes and agent contacts for AI agents — search by ZIP, by URL, or look up a zpid.
Redfin listings, sale-comps, and neighborhood market data via natural-language queries.
Related MCP Servers
- AlicenseCqualityDmaintenanceEnables access to Zillow real estate data through the Zillow Working API, allowing users to query property information and listings.1MIT
- AlicenseCqualityDmaintenanceEnables 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).37MIT
- AlicenseNot gradedqualityFmaintenanceProvides real-time access to Zillow real estate data, enabling property search, details, Zestimates, market trends, and mortgage calculations via natural language.7 npm48MIT
- AlicenseNot gradedqualityDmaintenanceIntegrates Zillow real estate data with AI assistants, enabling property search, neighborhood insights, and affordability calculations through natural language.7 npm1MIT