zillow-mcp 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Zillow — search listings, fetch property details, Zestimate history, saved searches & homes, market reports. Routes through the user's signed-in zillow.com tab via the fetchproxy browser extension to dodge bot detection.",
10
- "version": "0.2.0"
10
+ "version": "0.2.2"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Zillow",
16
16
  "source": "./",
17
17
  "description": "MCP server for Zillow — search listings, get property details, Zestimate history, saved searches & homes, market reports",
18
- "version": "0.2.0",
18
+ "version": "0.2.2",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zillow-mcp",
3
3
  "displayName": "Zillow",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Zillow real-estate access for Claude — search listings, get property details, Zestimate history, your saved searches & homes, market reports",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/bundle.js CHANGED
@@ -36668,6 +36668,40 @@ function textResult(data) {
36668
36668
  };
36669
36669
  }
36670
36670
 
36671
+ // src/next-data.ts
36672
+ var ParseError = class extends Error {
36673
+ constructor(message) {
36674
+ super(message);
36675
+ this.name = "ParseError";
36676
+ }
36677
+ };
36678
+ var OPEN_TAG_RE = /<script[^>]*id=["']__NEXT_DATA__["'][^>]*>/i;
36679
+ var CLOSE_TAG = "</script>";
36680
+ function extractNextData(html) {
36681
+ const openMatch = OPEN_TAG_RE.exec(html);
36682
+ if (!openMatch) {
36683
+ throw new ParseError("__NEXT_DATA__ script tag not found in HTML");
36684
+ }
36685
+ const start = openMatch.index + openMatch[0].length;
36686
+ const end = html.indexOf(CLOSE_TAG, start);
36687
+ if (end < 0) {
36688
+ throw new ParseError("__NEXT_DATA__ script tag has no closing </script>");
36689
+ }
36690
+ const json2 = html.slice(start, end).trim();
36691
+ try {
36692
+ return JSON.parse(json2);
36693
+ } catch (err) {
36694
+ throw new ParseError(
36695
+ `Failed to parse __NEXT_DATA__ JSON: ${err.message}`
36696
+ );
36697
+ }
36698
+ }
36699
+ function getPageProps(nextData) {
36700
+ const props = nextData.props;
36701
+ const pageProps = props?.pageProps;
36702
+ return pageProps ?? {};
36703
+ }
36704
+
36671
36705
  // src/tools/search.ts
36672
36706
  var HOME_TYPE_FILTERS = {
36673
36707
  house: "isSingleFamily",
@@ -36703,7 +36737,7 @@ function formatListing(raw) {
36703
36737
  url: url2
36704
36738
  };
36705
36739
  }
36706
- function buildSearchBody(input) {
36740
+ function buildSearchQueryState(input) {
36707
36741
  const filterState = {};
36708
36742
  switch (input.status ?? "for_sale") {
36709
36743
  case "for_rent":
@@ -36745,17 +36779,18 @@ function buildSearchBody(input) {
36745
36779
  }
36746
36780
  }
36747
36781
  return {
36748
- searchQueryState: {
36749
- usersSearchTerm: input.location,
36750
- filterState,
36751
- isMapVisible: false,
36752
- isListVisible: true
36753
- },
36754
- wants: { cat1: ["listResults"] },
36755
- requestId: 1,
36756
- isDebugRequest: false
36782
+ usersSearchTerm: input.location,
36783
+ filterState,
36784
+ isListVisible: true,
36785
+ isMapVisible: false
36757
36786
  };
36758
36787
  }
36788
+ function buildSearchPath(input) {
36789
+ const sqs = buildSearchQueryState(input);
36790
+ const slug = encodeURIComponent(input.location.trim());
36791
+ const qs = encodeURIComponent(JSON.stringify(sqs));
36792
+ return `/homes/${slug}_rb/?searchQueryState=${qs}`;
36793
+ }
36759
36794
  function registerSearchTools(server2, client2) {
36760
36795
  server2.registerTool(
36761
36796
  "zillow_search_properties",
@@ -36792,9 +36827,12 @@ function registerSearchTools(server2, client2) {
36792
36827
  }
36793
36828
  },
36794
36829
  async (input) => {
36795
- const body = buildSearchBody(input);
36796
- const data = await client2.fetchJson("/async-create-search-page-state/", { method: "POST", body });
36797
- const raw = data.cat1?.searchResults?.listResults ?? [];
36830
+ const path = buildSearchPath(input);
36831
+ const html = await client2.fetchHtml(path);
36832
+ const nextData = extractNextData(html);
36833
+ const pageProps = getPageProps(nextData);
36834
+ const sps = pageProps.searchPageState;
36835
+ const raw = sps?.cat1?.searchResults?.listResults ?? [];
36798
36836
  const limit = input.limit ?? 40;
36799
36837
  const formatted = raw.map(formatListing).filter((x) => x !== null).slice(0, limit);
36800
36838
  return textResult(formatted);
@@ -36802,40 +36840,6 @@ function registerSearchTools(server2, client2) {
36802
36840
  );
36803
36841
  }
36804
36842
 
36805
- // src/next-data.ts
36806
- var ParseError = class extends Error {
36807
- constructor(message) {
36808
- super(message);
36809
- this.name = "ParseError";
36810
- }
36811
- };
36812
- var OPEN_TAG_RE = /<script[^>]*id=["']__NEXT_DATA__["'][^>]*>/i;
36813
- var CLOSE_TAG = "</script>";
36814
- function extractNextData(html) {
36815
- const openMatch = OPEN_TAG_RE.exec(html);
36816
- if (!openMatch) {
36817
- throw new ParseError("__NEXT_DATA__ script tag not found in HTML");
36818
- }
36819
- const start = openMatch.index + openMatch[0].length;
36820
- const end = html.indexOf(CLOSE_TAG, start);
36821
- if (end < 0) {
36822
- throw new ParseError("__NEXT_DATA__ script tag has no closing </script>");
36823
- }
36824
- const json2 = html.slice(start, end).trim();
36825
- try {
36826
- return JSON.parse(json2);
36827
- } catch (err) {
36828
- throw new ParseError(
36829
- `Failed to parse __NEXT_DATA__ JSON: ${err.message}`
36830
- );
36831
- }
36832
- }
36833
- function getPageProps(nextData) {
36834
- const props = nextData.props;
36835
- const pageProps = props?.pageProps;
36836
- return pageProps ?? {};
36837
- }
36838
-
36839
36843
  // src/url.ts
36840
36844
  function urlToPath(input) {
36841
36845
  try {
@@ -37030,6 +37034,17 @@ function findSavedSearches(pageProps) {
37030
37034
  );
37031
37035
  }
37032
37036
  function findSavedHomes(pageProps) {
37037
+ const collections = pageProps.collectionsResponse;
37038
+ if (Array.isArray(collections)) {
37039
+ const flat = [];
37040
+ for (const c of collections) {
37041
+ if (Array.isArray(c.homes)) flat.push(...c.homes);
37042
+ else if (Array.isArray(c.properties)) flat.push(...c.properties);
37043
+ else if (Array.isArray(c.items)) flat.push(...c.items);
37044
+ }
37045
+ if (flat.length > 0) return flat;
37046
+ if (collections.length > 0) return [];
37047
+ }
37033
37048
  return findArrayByShape(
37034
37049
  pageProps,
37035
37050
  SAVED_HOME_KEYS,
@@ -37080,7 +37095,7 @@ function registerSavedTools(server2, client2) {
37080
37095
  inputSchema: {}
37081
37096
  },
37082
37097
  async () => {
37083
- const html = await client2.fetchHtml("/user/savedSearches/");
37098
+ const html = await client2.fetchHtml("/myzillow/SavedSearches");
37084
37099
  const nextData = extractNextData(html);
37085
37100
  const pageProps = getPageProps(nextData);
37086
37101
  const searches = findSavedSearches(pageProps);
@@ -37091,7 +37106,7 @@ function registerSavedTools(server2, client2) {
37091
37106
  "zillow_get_saved_homes",
37092
37107
  {
37093
37108
  title: "Get my saved (favorited) Zillow homes",
37094
- description: "The signed-in user's saved (favorited) homes on zillow.com. Returns address, price, Zestimate, status, and when each home was saved. Requires the user to be signed in. Read-only; safe to call repeatedly.",
37109
+ description: "The signed-in user's saved (favorited) homes on zillow.com, flattened across all of the user's collections. Returns address, price, Zestimate, status, and when each home was saved. Requires the user to be signed in. Read-only; safe to call repeatedly.",
37095
37110
  annotations: {
37096
37111
  title: "Get my saved (favorited) Zillow homes",
37097
37112
  readOnlyHint: true,
@@ -37101,7 +37116,7 @@ function registerSavedTools(server2, client2) {
37101
37116
  inputSchema: {}
37102
37117
  },
37103
37118
  async () => {
37104
- const html = await client2.fetchHtml("/myzillow/favorites/");
37119
+ const html = await client2.fetchHtml("/myzillow/favorites");
37105
37120
  const nextData = extractNextData(html);
37106
37121
  const pageProps = getPageProps(nextData);
37107
37122
  const homes = findSavedHomes(pageProps);
@@ -37111,35 +37126,34 @@ function registerSavedTools(server2, client2) {
37111
37126
  }
37112
37127
 
37113
37128
  // src/tools/market.ts
37114
- function pickMarketInfo(pageProps) {
37115
- const candidates = [
37116
- pageProps.marketInfo,
37117
- pageProps.marketReport,
37118
- pageProps.regionInfo,
37119
- pageProps.componentProps?.marketInfo
37120
- ];
37129
+ function pickRegion(pageProps) {
37130
+ const candidates = [pageProps.zhviRegion, pageProps.requestedRegion];
37121
37131
  for (const c of candidates) {
37122
37132
  if (c && typeof c === "object") return c;
37123
37133
  }
37124
37134
  return null;
37125
37135
  }
37126
- function format2(raw) {
37136
+ function pickAnalytics(pageProps) {
37137
+ const a = pageProps.odpMarketAnalytics;
37138
+ if (a && typeof a === "object") return a;
37139
+ return null;
37140
+ }
37141
+ function format2(region, analytics) {
37142
+ const yoy = typeof analytics?.zhviLatest?.zhviYoY === "number" ? Math.round(analytics.zhviLatest.zhviYoY * 1e3) / 10 : void 0;
37127
37143
  return {
37128
- region_id: raw.regionId,
37129
- region_name: raw.regionName,
37130
- region_type: raw.regionType,
37131
- median_sale_price: raw.medianSalePrice,
37132
- median_list_price: raw.medianListPrice,
37133
- median_rent_price: raw.medianRentPrice,
37134
- median_days_on_market: raw.medianDaysOnMarket,
37135
- inventory_count: raw.inventoryCount,
37136
- new_listings: raw.newListings,
37137
- pending_sales: raw.pendingSales,
37138
- zhvi: raw.zhvi,
37139
- zhvi_yoy_percent: raw.zhviYoYPercent,
37140
- for_sale_by_category: raw.forSaleByCategory,
37141
- buyer_seller_index: raw.buyerSellerIndex,
37142
- as_of_date: raw.asOfDate
37144
+ region_name: region?.name,
37145
+ region_type: region?.regionTypeName,
37146
+ parent_county: region?.parentCounty?.name,
37147
+ parent_state: region?.parentState?.name,
37148
+ median_sale_price: analytics?.mrktSaleLatest?.medianSalePrice,
37149
+ median_list_price: analytics?.mrktListingLatest?.medianListPrice,
37150
+ median_days_on_market: analytics?.mrktListingLatest?.medianDaysOnMarket,
37151
+ days_to_pending: analytics?.mrktSaleLatest?.daysToPending,
37152
+ new_listings: analytics?.mrktListingLatest?.newListings,
37153
+ for_sale_inventory: analytics?.mrktListingLatest?.forSaleInventory,
37154
+ zhvi: analytics?.zhviLatest?.zhvi,
37155
+ zhvi_yoy_percent: yoy,
37156
+ as_of_date: analytics?.zhviLatest?.asOfDate
37143
37157
  };
37144
37158
  }
37145
37159
  function pathFromInput(args) {
@@ -37157,7 +37171,7 @@ function registerMarketTools(server2, client2) {
37157
37171
  "zillow_get_market_report",
37158
37172
  {
37159
37173
  title: "Get Zillow market report for a region",
37160
- description: 'Market report for a Zillow region: median sale/list/rent prices, days on market, inventory, Zillow Home Value Index (ZHVI), year-over-year ZHVI change, and buyer/seller balance. Provide either a `region_path` (e.g. "/home-values/6181/brooklyn-ny/") or a full Zillow home-values URL. Read-only; safe to call repeatedly.',
37174
+ description: 'Market report for a Zillow region: median sale/list prices, days on market, for-sale inventory, new listings, Zillow Home Value Index (ZHVI), and year-over-year ZHVI change. Provide either a `region_path` (e.g. "/home-values/6181/brooklyn-ny/") or a full Zillow home-values URL. Read-only; safe to call repeatedly.',
37161
37175
  annotations: {
37162
37176
  title: "Get Zillow market report for a region",
37163
37177
  readOnlyHint: true,
@@ -37176,13 +37190,14 @@ function registerMarketTools(server2, client2) {
37176
37190
  const html = await client2.fetchHtml(path);
37177
37191
  const nextData = extractNextData(html);
37178
37192
  const pageProps = getPageProps(nextData);
37179
- const market = pickMarketInfo(pageProps);
37180
- if (!market) {
37193
+ const region = pickRegion(pageProps);
37194
+ const analytics = pickAnalytics(pageProps);
37195
+ if (!region && !analytics) {
37181
37196
  throw new Error(
37182
- `Could not locate marketInfo in __NEXT_DATA__ at ${path}.`
37197
+ `Could not locate market data (zhviRegion + odpMarketAnalytics) in __NEXT_DATA__ at ${path}.`
37183
37198
  );
37184
37199
  }
37185
- return textResult(format2(market));
37200
+ return textResult(format2(region, analytics));
37186
37201
  }
37187
37202
  );
37188
37203
  }
@@ -37266,7 +37281,7 @@ function registerMortgageTools(server2) {
37266
37281
  }
37267
37282
 
37268
37283
  // src/index.ts
37269
- var VERSION = "0.2.0";
37284
+ var VERSION = "0.2.2";
37270
37285
  var port = process.env.ZILLOW_WS_PORT ? Number(process.env.ZILLOW_WS_PORT) : void 0;
37271
37286
  var transport = new FetchproxyTransport({ port, version: VERSION });
37272
37287
  var client = new ZillowClient({ transport });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zillow-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "mcpName": "io.github.chrischall/zillow-mcp",
5
5
  "description": "Zillow MCP server for Claude — developed and maintained by AI (Claude Code)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.chrischall/zillow-mcp",
4
- "description": "Zillow real-estate access for Claude — search listings, fetch property details, Zestimate history, saved searches & homes, market reports. Auth via the user's signed-in zillow.com browser tab through the fetchproxy extension.",
4
+ "description": "Zillow real-estate for Claude — search, property details, Zestimates, saved searches & homes",
5
5
  "repository": {
6
6
  "url": "https://github.com/chrischall/zillow-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.2.0",
9
+ "version": "0.2.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "zillow-mcp",
14
- "version": "0.2.0",
14
+ "version": "0.2.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },