Market snapshot
The simplest possible Estaite example. One tool call —
get_estaite_cbsa_overview — returns the
average rent, vacancy, year-over-year growth, and the list of submarkets
inside a metro area.
The code
Save as market-snapshot.py:
"""Market snapshot — give it a city, get a rental market summary.
Setup: pip install mcp export ESTAITE_API_KEY="mk_..."
Usage: python market-snapshot.py --city "Austin""""
import argparseimport asyncioimport jsonimport osimport sys
from mcp import ClientSessionfrom mcp.client.streamable_http import streamablehttp_client
MCP_URL = "https://mcp.estaite.com"
async def fetch_snapshot(api_key: str, city: str) -> dict: headers = {"x-api-key": api_key} async with streamablehttp_client(MCP_URL, headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool( "get_estaite_cbsa_overview", {"cbsa": city}, ) if not result.content: raise RuntimeError("Empty response from Estaite") return json.loads(result.content[0].text)
def print_snapshot(city: str, data: dict) -> None: print() cbsa_name = data.get("cbsa_name") or city state = data.get("state") or "" as_of = data.get("as_of") or "" print(f" ── {cbsa_name}{', ' + state if state else ''} rental market ──") if as_of: print(f" as of {as_of}") print()
if data.get("error"): print(f" No data: {data['error']}") return
summary = data.get("cbsa_summary") or {} apts_2br = (summary.get("segments") or {}).get("apartments", {}).get("br2", {}) avg_rent = apts_2br.get("avg_rent") avg_yoy = apts_2br.get("avg_yoy") avg_vac = summary.get("avg_vacancy_rate") avg_inc = summary.get("avg_household_income") n_subs = data.get("submarket_count", len(data.get("submarkets", [])))
# YoY and vacancy come back as whole-percent (e.g. 3.4 means 3.4%), not decimals. if avg_rent is not None: print(f" Avg apt 2BR rent: ${avg_rent:,.0f}/mo") if avg_yoy is not None: print(f" Avg apt 2BR YoY: {avg_yoy:+.1f}%") if avg_vac is not None: print(f" Avg vacancy: {avg_vac:.1f}%") if avg_inc is not None: print(f" Avg household income: ${avg_inc:,.0f}") print(f" Submarkets covered: {n_subs}") print()
# Top 5 submarkets by 2BR apartment rent subs = data.get("submarkets", []) if subs: def br2_rent(s): return ((s.get("segments") or {}).get("apartments") or {}).get("br2", {}).get("median_rent") or 0 top = sorted(subs, key=br2_rent, reverse=True)[:5] print(" Top 5 submarkets by apt 2BR median rent:") for s in top: name = (s.get("name") or "—")[:34] rent = br2_rent(s) if rent: print(f" {name:35s} ${rent:,.0f}/mo") print()
if data.get("attribution"): print(f" {data['attribution']}") print()
def main(): parser = argparse.ArgumentParser(description="Rental market snapshot for a US city/metro") parser.add_argument("--city", required=True, help='City or metro name (e.g. "Austin")') args = parser.parse_args()
api_key = os.environ.get("ESTAITE_API_KEY") if not api_key: print("ERROR: ESTAITE_API_KEY env var not set.") sys.exit(1)
print(f"\nFetching market snapshot for {args.city}...") data = asyncio.run(fetch_snapshot(api_key, args.city)) print_snapshot(args.city, data)
if __name__ == "__main__": main()Run it
python market-snapshot.py --city "Austin"Sample output
Fetching market snapshot for Austin...
── Austin-Round Rock-San Marcos, TX rental market ── as of 202603
Avg apt 2BR rent: $1,672/mo Avg apt 2BR YoY: -4.3% Avg vacancy: 3.8% Avg household income: $116,174 Submarkets covered: 32
Top 5 submarkets by apt 2BR median rent: Tarrytown / Clarksville $2,465/mo East Cesar Chavez $2,438/mo Dripping Springs $1,956/mo Lakeway / Bee Cave $1,894/mo Del Valle / Easton Park $1,872/mo
Data provided by Estaite Solutions (estaite.com)(Numbers shift each month as the data refreshes — yours may differ.)
What’s happening
session.initialize()— MCP protocol handshake. Every session starts with this.session.call_tool("get_estaite_cbsa_overview", {"cbsa": city})— single call to fetch the entire metro view, including submarkets.- The tool returns a JSON string in
result.content[0].text; we parse it and pretty-print the parts we care about.
A note on units
Estaite returns percentages as whole-percent values (e.g. 3.4 means
3.4%, not 0.034). Don’t multiply by 100 before formatting — you’ll get
absurd numbers like “340%”. This applies to avg_yoy, avg_vacancy_rate,
and any other field with _rate, _yoy, or _change in the name.
Next steps
- Switch metros without restarting the session. Wrap the call in a loop and pass different
cbsavalues inside the samewithblock — saves the handshake overhead. - Drill into a single submarket. Take an
idfromdata["submarkets"]and feed it toquery_estaite_submarket_indexfor full per-segment metrics. - Compare two metros side by side. Run two
get_estaite_cbsa_overviewcalls and diff thecbsa_summaryblocks.