realestate-mcp
This server integrates with Claude to search and retrieve Australian property listings from realestate.com.au, automatically handling anti-bot protection with a real Chrome browser.
Search listings: Find properties for sale, rent, or recently sold by location, with filters for price range, bedrooms, bathrooms, car spaces, land size, and property type (e.g., house, apartment, townhouse, villa, land, acreage, unit block). Exclude under-contract listings. Results include exact and surrounding suburbs, up to 25 per page.
Get listing details: Fetch comprehensive info (via URL or ID) including description, photos, floorplans, agent contacts, agency details, inspection times, auction dates, and suburb market insights.
Resolve locations: Convert vague/partial names (e.g., "bondi") into canonical suburb names with state and postcode for accurate searches.
View photos:
get_listing_photos(documented but not in current schema) retrieves image data at various resolutions to evaluate condition and layout; floorplans can be included.Bot bypass: Uses a warmed-up Chrome instance to navigate realestate.com.au's Kasada bot detection.
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., "@realestate-mcpfind me 3-bed houses in Bondi under $5m"
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.
sydney-rental-data-pipeline
The data pipeline behind SydneyRealEstateFindings: it searches
realestate.com.au through a real Chrome, measures how far every listing is from where Errol
needs to be, scores it, publishes its photos, and writes the result into the findings repo's
data/. The site next door renders that and nothing else (its docs/adr/0005).
It is also an MCP server, thinly: search_listings and get_listing for asking questions from
Claude Code. That used to be the whole program; now it is one subcommand.
SydneyRentalDataPipeline/ this repo
src/ REA scrape + parse, geocode, route (Valhalla/ORS/Google), TfNSW,
images — and cli.ts, mcp.ts, setup.ts
src/stages/ the run, one module per step, each exporting main(argv)
src/lib/ scoring, ledger, search planning, walkability, photos/R2
test/ node --test suites (the former check:* scripts)
.env every key: TFNSW_API_KEY, GOOGLE_MAPS_API_KEY, R2_*, …
writes to → ../SydneyRealEstateFindings/data/ (and its public/images mirror)
SydneyRealEstateFindings/ the site: src/ + data/ + .env.local, five self-checks
packages/schema/ the zod schema both repos depend onThe two checkouts must sit side by side. The schema lives with the data it describes, as the
sydney-rental-schema package inside the findings repo, and this one depends on it by path
(file:../SydneyRealEstateFindings/packages/schema), so npm links it rather than copying it.
FINDINGS_DIR relocates the data, not the code or the package. The moves are recorded in
MIGRATION.md (Phase 1) and PHASE2.md (Phase 2).
Requirements
Node.js 20.12+ (
process.loadEnvFile)Google Chrome installed (the real browser — see How it works)
The findings repo checked out as
../SydneyRealEstateFindings, with its ownnpm install(that is what buildspackages/schema, and the linked package resolveszodfrom there — both repos pin 4.4.3)
Related MCP server: zillow-mcp
Setup
npm install && npm run build
cp .env.example .env # then fill in the keys — see Configuration
node dist/cli.js setup # one-time warm-up: a Chrome window opens for a few secondsRegister the MCP adapter with Claude Code (user scope, so it works from either repo):
claude mcp add realestate -s user -- node "E:/Personal Projects/SydneyRealEstate/SydneyRentalDataPipeline/dist/cli.js" mcpRunning the pipeline
One entry point, node dist/cli.js <command>; every npm run script in package.json is an
alias for one of them, kept so the run protocol's commands still read the same.
Command | npm alias | What it does |
|
| Warm the Chrome profile once |
|
| Search REA per |
|
| Map a capture into a run: ledger merge, scores, photos to R2, |
|
| Record the capture's listings that have no street address in |
|
| Rebuild a committed run from its capture. Byte-identical when nothing changed — the migration's own proof |
|
| Derive the search envelope (findings |
|
| Add walkability / routed travel / transit legs to the ledger |
|
|
|
|
| Validate the findings repo's |
|
| Reports; nothing written |
|
| Destroy runs, photos and knowledge. Dry run without |
|
| Serve the MCP adapter over stdio |
| — | The whole run in one process: absence gate → build → enrich → replay → validate, stopping at the two human gates. |
Paths handed to a command — a capture, --out, --cache, --places-out — resolve against the
current directory, so prefer absolute ones. The committed runs' captures live at
E:/Personal Projects/SydneyRealEstate/captures/.
run — the whole thing at once
node dist/cli.js run --capture="E:/Personal Projects/SydneyRealEstate/captures/<capture>.json"
# … the absence gate prints its table and stops …
node dist/cli.js run --resumerun does capture → absence gate → build → enrich walk · travel · transit → replay → validate
in one process, keeping its place in scratch/run-state.json. The replay is the step it exists
to stop you forgetting: the enrichers write the ledger, because a routed minute is a fact
about a place rather than about a moment, and a replay is what folds their answers into the run
just built.
It stops at both of AGENT.md's human gates:
Absence — it fetches every active-but-absent listing's page, writes proposed verdicts into the capture's
gonemap, prints the table and stops. "Page gone" and "leased" are the same evidence read two ways, so the reading is yours. Anything it could not reach gets no verdict and is left out of the map.--skip-absenceskips the whole step; say so in the commentary.Commentary — checked at the end.
runwill not call a run ready to commit without it.
--dry-run walks all of it and writes nothing. --no-enrich stops after the build. --search
is the explicit opt-in to taking a fresh capture, which is a real pass against REA — ask Errol
first, as everywhere else here.
The protocol around it — what to commit, and what to say in the commentary — is the findings
repo's AGENT.md; the run happens here and commits there, because data/ is versioned there.
Before a data commit, this repo's gate is:
npm run typecheck && npm run build && npm test && npm run validate:datavalidate:data -- --check-remote also verifies every photo path against R2 when the keys are
in. The findings repo has its own, smaller gate for the site.
The MCP adapter
What Claude Code talks to. Interactive use only: the pipeline calls the same functions
directly — get_listing is src/lib/listing-detail.ts, which run's absence gate calls too.
Tool | What it does |
| Search buy / rent / sold by location, with price, bed, bath, car, land-size and property-type filters. 25 results per page. Optionally narrows by real routed travel time from an origin — walk, drive, or public transport — and attaches the measured minutes to every listing. |
| Full detail for one listing — description, all photos, floorplans, agents, agency ratings, suburb market insights. Takes a URL or bare listing ID. |
| Returns the actual photographs as images, so the model can judge condition, finish quality, natural light and layout rather than just numbers. Optionally includes the floorplan. |
| Turn |
geocode_places and route_places used to be tools too. Their only caller was the pipeline,
which no longer needs a wire to reach them.
Returned fields
Address (with suburb/state/postcode), price display, bedrooms, bathrooms, car spaces, studies, land and building size, property type, agency (with average rating and review count), agents (name, job title, profile URL), inspection times and auction dates as ISO 8601 with offset, description, photo and floorplan URLs.
On a walk, a route that is really a ferry crossing carries mislabelled — the implied speed that gave it away, the threshold it beat, and confirmation from the timetable that a ferry serves that address. The minutes are left exactly as the router returned them; the flag is the interpretation, kept separate so you can disagree with it. Needs TFNSW_API_KEY, and is skipped without one. On transit answered by TfNSW, each leg's journey reports what the trip is actually made of. See CLAUDE.md for both.
Results include listings REA blends in from neighbouring suburbs; those are flagged isSurrounding: true. They can be across water from the suburb you searched, so filter on each listing's own suburb for anything geographic.
Photos and token cost
get_listing_photos sends real image data into the conversation, which is not free. REA's image URLs carry a {size} placeholder, so resolution is chosen at the CDN rather than downscaled locally. Cost is roughly (width × height) / 750 tokens per image:
| ~tokens each | 6 images |
| 100 | 600 |
| 230 | 1.4k |
| 410 | 2.5k |
| 640 | 3.8k |
| 1050 | 6.3k |
Keep limit small. It's a separate tool from get_listing precisely so routine lookups don't drag a dozen images into context.
Note that many REA listings are virtually staged — the furniture is digitally inserted and the listing usually says so in the description. Rooms are real, furnishings are not; use the floorplan for dimensions.
How it works
realestate.com.au is behind Kasada bot protection. Everything in src/ is shaped by that:
Raw HTTP does not work. Every endpoint — including
lexa.realestate.com.au/graphqlandservices.realestate.com.au— returns429with ax-kpsdk-ctheader. Those APIs are scoped to signed mobile-app tokens.Headless does not work from a cold start. Plain Playwright, Playwright with anti-automation flags, and even patched Chrome all get
429on a fresh profile.What works: patchright driving your real installed Chrome with a persistent profile, warmed up headed once. Kasada issues its challenge, the browser solves it, and the token persists in the profile directory. Headless works from then on.
That's why setup opens a visible window once, and why bundled Chromium isn't used — it's fingerprinted and blocked.
Data is read from the page's server-side hydration blob rather than the DOM:
<script>window.ArgonautExchange={ "resi-property_listing-experience-web": {
"urqlClientCache": "{\"<hash>\":{\"data\":\"{…}\"}}" }}</script>
└─> buySearch.results.exact.items[].listing ← the real Lexa GraphQL objectsTwo things that will bite you if you modify the parser:
The app deletes
window.ArgonautExchangeafter hydration. Reading the live global returnsundefinedon a page that loaded perfectly fine. Parse the<script>tag's source text.Results are split into
results.exactandresults.surrounding. Reading onlyexactsilently drops up to two-thirds of a page.
Configuration
Everything lives in .env at this package's root — copy .env.example. It is read from the
package root whichever directory a command is run from, and whoever starts the process: a script,
the CLI, or Claude Code spawning the MCP adapter. What is already in the environment wins over
the file, so keep the MCP entry's env block empty. Keys belong in .env; it is gitignored.
Env var | Default | Purpose |
|
| The site whose |
|
| Where the warm browser profile lives |
|
| Browser channel; |
|
| Navigation timeout in ms |
|
| Road modes only — |
| (unset) |
|
|
|
|
|
| The geocode + route cache. Point it at the findings repo's |
| — | Transport for NSW Trip Planner key, and what answers |
| — | Server key with the Routes API and Geocoding API enabled. Required for |
| — | Only read when |
|
| Idle ms before the browser closes and releases the profile lock |
| — | Cloudflare R2, where |
The REALESTATE_MCP_* names predate the rename and are hand-maintained in two .env files;
they stay.
Photo hosting
Listing photos are not in git. build publishes them to a Cloudflare R2 bucket and
mirrors them into the findings repo's public/images/listings/ (gitignored there) so
validate can check them and so a re-upload never means re-downloading from REA.
The findings repo's PLAN.md §2 originally committed photos to the repo, with §11 holding an
escape hatch open — every image path in JSON is site-relative and every render goes through the
site's src/lib/images.ts, so moving the files costs one env var and zero JSON changes. That
hatch was taken before the first real run rather than after, because photos entering git
history is the one part that would have been expensive to undo. R2 rather than the alternatives
because egress is free, and the site's whole job is serving the same photos to phones over
and over.
One-time setup
Bucket — Cloudflare dashboard → R2 → Create bucket, any name — it goes in
R2_BUCKET. Location Automatic (or APAC).Public access — one of:
Public Development URL (bucket → Settings): free, instant, gives
https://pub-<hash>.r2.dev. Cloudflare rate-limits it and labels it development-only — fine for a handful of viewers.Custom domain: needs a domain on Cloudflare. Proper CDN caching, no rate limit. Better if you have one.
API token — R2 → Manage API tokens → Create API token, permission Object Read & Write, scoped to that bucket. Copy the Access Key ID and Secret Access Key; the secret is shown once.
Account ID — on the R2 overview page.
.envat this package's root (gitignored, never reaches Vercel) — copy.env.exampleand fill:R2_ACCOUNT_ID=… R2_ACCESS_KEY_ID=… R2_SECRET_ACCESS_KEY=… R2_BUCKET=<your-bucket> R2_PUBLIC_BASE_URL=https://pub-<hash>.r2.devVerify —
npm run check:r2. It uploads a real WebP, reads it back over the public URL the way a phone would, and cleans up. The public read is the part worth having: a token can write perfectly while public access is still off, and you would not find out until photos 404 on the live site.Site env — set
NEXT_PUBLIC_IMAGE_BASE_URLin the findings repo (.env.local, and its Vercel project) to the same public base URL.
No CORS configuration is needed: photos are rendered with plain <img> tags, not fetched.
build refuses to run without R2 configured, rather than writing image paths that would
resolve to nothing (--local-images skips uploading, for local testing only). A photo is only
recorded once it has uploaded and been mirrored — upload happens first, so a failed upload
leaves nothing behind for a later run to mistake for a real file.
Development
Two configs, one command. npm run build runs both: tsconfig.json compiles src/ to dist/,
which is what the CLI and the MCP adapter run, and tsconfig.test.json compiles src/ and
test/ together to dist-test/, which node --test runs. npm run typecheck is the second
one with --noEmit. Nothing compiles TypeScript at run time any more — there is no tsx here.
npm test is node dist/cli.js check: 174 assertions over the scoring model, the ledger merge,
search planning, walkability geometry and the transit classifier. They print their working out
as well as passing, because that is what they are for — read check:scoring's arithmetic before
trusting a run's numbers.
Everything under src/stages/ and src/lib/ came from the findings repo's scripts/ and still
computes what it computed there. The proof is replay reproducing both committed runs byte for
byte, which is worth re-running after any change under src/lib/:
node dist/cli.js replay "E:/Personal Projects/SydneyRealEstate/captures/2026-08-24-walk15.json" --run-id=2026-08-24a
node dist/cli.js replay "E:/Personal Projects/SydneyRealEstate/captures/2026-08-24-transit25.json" --run-id=2026-08-25a
git -C ../SydneyRealEstateFindings diff --stat data/ # must print nothing(git status there will still flag the two files — the script writes LF and the working copy
is CRLF. git checkout -- data/runs/ puts them back.)
The MCP adapter runs compiled JS as a child of Claude Code — so edits to src/ do nothing until
that process is replaced:
./scripts/reload-mcp.ps1Rebuilds, kills any Chrome holding the browser profile, kills the server process(es), and reports whether the profile is still warm. -CheckOnly inspects without changing anything; -NoBuild skips compilation. It aborts before killing anything if the build fails.
It only targets node processes referencing this repo's dist/cli.js and Chrome processes referencing the realestate-mcp profile — your normal browser is untouched.
Three different actions for three different changes:
Changed | Action |
Behaviour of an existing tool | reload script — the server respawns on the next tool call |
Added / renamed a tool, or changed its input schema | restart Claude Code |
Calls fail with the bot-protection error |
|
The MCP tool list is negotiated once during the connection handshake and cached for the session. Killing the server picks up new code immediately, but a newly added tool stays invisible until Claude Code restarts.
setup is independent of both — it warms a Kasada token into the profile directory, which survives restarts and reboots.
One profile, one process. Chrome takes an exclusive lock on its user-data-dir. capture
uses the same profile as the MCP adapter on purpose (it is the warm one), so a capture cannot run
while Claude Code has the server up — disconnect it, or run the reload script first. Ad-hoc
scripts that call fetchPage should point at their own profile:
$env:REALESTATE_MCP_PROFILE = "<scratch dir>"Verify new URL filters by result count, not status code. REA silently ignores malformed filter segments and still returns HTTP 200 with a normal-looking page. See CLAUDE.md for the segment grammar.
Troubleshooting
"Blocked by realestate.com.au bot protection" — the profile has gone cold. Re-run node dist/cli.js setup. Token lifetime is not documented; expect to re-warm occasionally. Check current state without changing anything with ./scripts/reload-mcp.ps1 -CheckOnly.
"Could not open the browser profile — another process is using it" — Chrome allows one process per profile directory. Something else holds it: a setup run still open, a stray Chrome, a capture in progress, or the MCP adapter Claude Code spawned. ./scripts/reload-mcp.ps1 clears it.
setup appears to do nothing, and tools stay blocked — this was a real bug, now fixed. The server used to hold the profile lock for its whole lifetime, so setup in another terminal could never acquire it, and the running Chrome kept serving the cookies it loaded at startup. The browser now closes after REALESTATE_MCP_IDLE, and fetchPage retries once with a fresh context when it hits a block.
"Could not launch Google Chrome" — install Chrome, or set REALESTATE_MCP_CHANNEL=msedge.
Warm-up fails repeatedly — rapid requests raise your Kasada score and it takes a while to decay. Wait a few minutes, or try from a different network.
A filter seems to be ignored — it probably is. REA drops malformed filter segments silently and still returns 200. Compare totalResults against an unfiltered search.
Cannot find module 'sydney-rental-schema' — the findings repo is not checked out beside this one, or has not had npm install run in it. This package depends on file:../SydneyRealEstateFindings/packages/schema, and that install is what builds the dist/ the link points at. FINDINGS_DIR relocates the data, not the package.
the test suites are not built — npm run build. It builds both configs; dist-test/ is where node --test looks.
"R2 is not configured — missing … in the pipeline's .env" — fill the five R2_* lines in .env (see Photo hosting). build and reset refuse without them; validate only needs them with --check-remote.
Available Tools
3 toolsget_listingGet a single listingA
Fetch full detail for one realestate.com.au listing, including the complete description, all photos, floorplans, agency and agent contacts. Accepts a listing URL or a bare numeric listing ID.
| Name | Required | Description | Default |
|---|---|---|---|
| listing | Yes | Full URL or numeric ID, e.g. "151132896" or a realestate.com.au property URL |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the output contents (complete description, photos, floorplans, agency/agent contacts) and accepts input formats. However, it omits any discussion of error handling, rate limits, or what happens when a listing is unavailable or invalid, which are notable gaps for an unannotated tool.
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 description is two sentences, front-loaded with the primary action 'Fetch full detail' and immediately specifying the resource. It avoids fluff, making every word count while remaining concise and well-structured.
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 simple one-parameter fetch tool, the description adequately explains the input format and the key output components (photos, floorplans, contacts). However, it lacks details about potential error conditions or whether the listing must be currently active, which would round out the context.
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?
The schema provides a full description of the sole parameter (listing) with examples of URL and numeric ID, achieving 100% coverage. The tool description repeats the same information without adding new meaning, so it does not enhance the schema beyond what is already documented.
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 clearly states the tool fetches full detail for one realestate.com.au listing, enumerating specific content like photos, floorplans, and contacts. This distinguishes it from sibling tools such as search_listings, which likely return multiple listings, by focusing on a single record.
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?
The description implies usage when you have a specific listing URL or numeric ID, providing clear context for when to use this tool. However, it does not explicitly name alternatives or state when not to use it, though the single-listing focus inherently guides appropriate selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_locationResolve a locationA
Turn partial or ambiguous location text into canonical REA suburb/region names with state and postcode. Fast (no browser needed). Use this to disambiguate before search_listings — e.g. "bondi" returns Bondi, Bondi Beach, Bondi Junction.
| Name | Required | Description | Default |
|---|---|---|---|
| max | No | ||
| query | Yes | Partial location, e.g. "bondi" or "3121" |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It discloses performance ('Fast, no browser needed') and the behavior of returning multiple canonical names for ambiguous input. It doesn't cover edge cases like no-match, but the core behavior is well disclosed for a simple resolver.
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?
Two sentences, front-loaded with the primary purpose, followed by a usage suggestion and example. Every sentence adds value, and the structure is highly readable and efficient.
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?
The description fully explains what the tool does and how to use it relative to siblings. It doesn't include the exact return format or error behavior, but for a simple resolver, the information given is sufficient. The example adds context for ambiguity handling.
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?
The schema provides a description for 'query' but not for 'max'. The tool description adds meaning by explaining query as partial/ambiguous location text and showing an example with multiple results, which implies max controls the result count. However, max's semantics are not explicitly explained, and with only 50% schema coverage, the description only partially compensates.
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 clearly states the tool's function: turning partial/ambiguous location text into canonical REA suburb/region names with state and postcode. It uses a specific verb+resource and differentiates itself from the sibling search_listings and get_listing tools.
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?
Explicitly tells when to use the tool ('before search_listings') and provides a concrete example ('bondi' returns multiple suburbs). It also mentions it's fast, giving a reason to prefer it for disambiguation, and names an alternative, making the usage context clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_listingsSearch realestate.com.au listingsA
Search realestate.com.au for properties to buy, rent, or recently sold. Returns up to 25 listings per page with address, price, bed/bath/car counts, land size, agency, agents, inspection times and auction dates. Call resolve_location first if the user's location is vague or misspelled.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| channel | No | buy = for sale, rent = rentals, sold = recently sold | buy |
| location | Yes | Suburb/postcode/region, e.g. "Bondi, NSW 2026" or "Richmond, VIC 3121" | |
| maxPrice | No | AUD. For rent this is weekly rent. | |
| minPrice | No | AUD. For rent this is weekly rent. | |
| minBedrooms | No | ||
| minLandSize | No | Square metres | |
| minBathrooms | No | ||
| minCarSpaces | No | ||
| propertyTypes | No | ||
| excludeUnderContract | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full transparency burden. It discloses pagination limits ('up to 25 listings per page') and the return fields (address, price, bed/bath/car counts, land size, agency, agents, inspection times, auction dates), which is valuable context. It does not mention rate limits, error behaviors, or whether results are sorted, but for a search tool this is adequate.
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 description is three sentences: the first states the main purpose, the second details the return value, and the third gives a usage prerequisite. Every sentence adds meaningful information without redundancy or filler.
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?
Given the tool's complexity (11 parameters, no output schema, no annotations), the description covers the most critical aspects: what it returns, pagination behavior, and a key prerequisite. However, it does not mention channel-specific parameter relevance (e.g., minLandSize for rentals) or the default channel, leaving some ambiguity for agents constructing sophisticated queries.
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 45%, so the description must compensate. It adds context about the result fields and pagination, which helps infer how parameters like maxPrice and minBedrooms affect results. However, it does not explain the semantics of several parameters with no schema description (e.g., excludeUnderContract, propertyTypes), though their names are self-explanatory. Overall, it partially compensates but not fully.
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 clearly states the tool searches realestate.com.au for properties to buy, rent, or recently sold. The verb 'search' with the specific resource and the channel options (buy/rent/sold) make its purpose unambiguous and distinguish it from sibling tools like get_listing and resolve_location.
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 explicitly instructs the agent to call resolve_location first when the user's location is vague or misspelled, which is a clear directive for when to use an alternative. However, it does not mention when to use get_listing (e.g., for retrieving details of a single listing), leaving that distinction implied by the name rather than explained.
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.
3 tool updates
v0.1.0- First observed
get_listing - First observed
resolve_location - First observed
search_listings
TDQS
Scored across 3 tools
Each tool serves a distinct purpose: searching listings, fetching a specific listing's full details, and resolving ambiguous locations. There is no overlap between them, and the descriptions clearly differentiate their roles.
All tool names follow a consistent verb_noun pattern in snake_case: search_listings, get_listing, resolve_location. The naming is predictable and aligns with each tool's function.
With only 3 tools, the set is tightly scoped for a real estate listing search and detail service. Each tool is necessary and there is no bloat, making it easy for an agent to understand and use the server effectively.
The core workflow of searching, resolving locations, and viewing listing details is well-covered. A minor gap is the lack of explicit pagination support in search_listings, but this is not a significant limitation for most use cases.
Maintenance
Related MCP Connectors
Pull property listings, prices, and details from real-estate sites as structured JSON.
Redfin for-sale, for-rent and sold listings plus full property pages, as structured JSON.
Australian property and suburb data: valuations, sales, rents, schools, risk and zoning.
Web search, browser automation, scraping, crawling and CAPTCHA solving for AI agents.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables access to Australian real estate data through the Realty In Au API, supporting property listings, agent/agency information, property details, school lookups, and property search with various filters.13MIT
- AlicenseAqualityAmaintenanceEnables natural-language access to Zillow real-estate data, including property search, details, Zestimate history, saved searches/homes, and market reports, by routing requests through the user's authenticated browser session.520369 npmMIT
- AlicenseAqualityAmaintenanceEnables natural language access to Redfin real estate data, including property search, details, photos, market reports, price history, climate risk, and saved homes/searches, by routing requests through your own signed-in browser session.21417 npm3MIT
- AlicenseAqualityBmaintenanceEnables AI assistants to search and retrieve used and new car listings from carsales.com.au, with support for various filters and detailed listing information including images. It uses a real browser to bypass bot protection and integrates with MCP-compatible clients like Claude Desktop and Claude Code.192MIT