zillow-mcp 0.1.0 → 0.2.1

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.1.0"
10
+ "version": "0.2.1"
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.1.0",
18
+ "version": "0.2.1",
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.1.0",
4
+ "version": "0.2.1",
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/README.md CHANGED
@@ -17,13 +17,13 @@ None of them can see what *you* have saved, favorited, or recently viewed — be
17
17
 
18
18
  | Tool | Purpose | Auth-scoped |
19
19
  | --- | --- | :---: |
20
- | `zillow_search_properties` | Search listings by location and filters | |
21
- | `zillow_get_property` | Full record for a zpid (price, Zestimate, beds, schools, history) | |
22
- | `zillow_get_zestimate_history` | Time series of Zestimate values | |
23
- | `zillow_get_saved_searches` | Your saved searches with new-listing counts | ✓ |
24
- | `zillow_get_saved_homes` | Your favorited homes | ✓ |
25
- | `zillow_get_market_report` | Median price, days on market, ZHVI for a region | |
26
- | `zillow_calculate_mortgage` | Local PITI calculator (no network) | |
20
+ | `zillow_search_properties` | Search listings by location, status, price band, beds/baths, home type | |
21
+ | `zillow_get_property` | Full record for a zpid (price, Zestimate, beds, schools, neighborhood, price history) | |
22
+ | `zillow_get_zestimate_history` | Time series of Zestimate values (and rent Zestimate where available) | |
23
+ | `zillow_get_saved_searches` | Your saved searches with new-listing counts and notification frequency | ✓ |
24
+ | `zillow_get_saved_homes` | Your favorited homes with current price + Zestimate | ✓ |
25
+ | `zillow_get_market_report` | Median sale/list/rent, days on market, inventory, ZHVI for a region | |
26
+ | `zillow_calculate_mortgage` | Local PITI calculator — principal+interest, taxes, insurance, HOA, PMI (no network) | |
27
27
 
28
28
  ## Install
29
29
 
package/dist/bundle.js CHANGED
@@ -34704,10 +34704,7 @@ var ZillowClient = class {
34704
34704
  );
34705
34705
  }
34706
34706
  throwIfSignInPage(result) {
34707
- const signInMarkers = ["/user/login", "captcha-delivery"];
34708
- const looksLikeSignIn = /\/user\/login/.test(result.url) || /[?&]login=true/.test(result.url) || signInMarkers.some(
34709
- (m) => result.body.includes(m) && result.body.length < 8e4
34710
- );
34707
+ const looksLikeSignIn = /\/user\/login/.test(result.url) || /[?&]login=true/.test(result.url) || result.body.includes("captcha-delivery") && result.body.length < 8e4;
34711
34708
  if (looksLikeSignIn) throw new SessionNotAuthenticatedError();
34712
34709
  }
34713
34710
  };
@@ -36763,8 +36760,14 @@ function registerSearchTools(server2, client2) {
36763
36760
  server2.registerTool(
36764
36761
  "zillow_search_properties",
36765
36762
  {
36766
- description: "Search Zillow listings by location (city, ZIP, neighborhood, or address) and optional filters. Returns matching properties with price, beds/baths, sqft, Zestimate, and the homedetails URL. Does NOT return Zestimate history \u2014 use zillow_get_zestimate_history for that.",
36767
- annotations: { readOnlyHint: true },
36763
+ title: "Search Zillow listings",
36764
+ description: "Search Zillow listings by location (city, ZIP, neighborhood, or address) and optional filters (status, price band, beds/baths minimums, home types). Returns matching properties with price, beds/baths, sqft, Zestimate, status, image, and homedetails URL. Does NOT return Zestimate history \u2014 use zillow_get_zestimate_history for that. Read-only; safe to call repeatedly.",
36765
+ annotations: {
36766
+ title: "Search Zillow listings",
36767
+ readOnlyHint: true,
36768
+ idempotentHint: true,
36769
+ openWorldHint: true
36770
+ },
36768
36771
  inputSchema: {
36769
36772
  location: external_exports.string().describe(
36770
36773
  'Free-text location: city, ZIP, neighborhood, or address (e.g. "Brooklyn, NY", "94110", "Park Slope")'
@@ -36833,6 +36836,16 @@ function getPageProps(nextData) {
36833
36836
  return pageProps ?? {};
36834
36837
  }
36835
36838
 
36839
+ // src/url.ts
36840
+ function urlToPath(input) {
36841
+ try {
36842
+ const u = new URL(input);
36843
+ return `${u.pathname}${u.search}`;
36844
+ } catch {
36845
+ return input.startsWith("/") ? input : `/${input}`;
36846
+ }
36847
+ }
36848
+
36836
36849
  // src/tools/properties.ts
36837
36850
  function findPropertyInPageProps(pageProps) {
36838
36851
  const cacheRaw = pageProps.gdpClientCache ?? pageProps.componentProps?.gdpClientCache;
@@ -36843,23 +36856,19 @@ function findPropertyInPageProps(pageProps) {
36843
36856
  } catch {
36844
36857
  return null;
36845
36858
  }
36859
+ for (const [key, v] of Object.entries(cache)) {
36860
+ if (key.startsWith("Property:") && v && typeof v === "object" && v.property) {
36861
+ return v.property;
36862
+ }
36863
+ }
36846
36864
  for (const v of Object.values(cache)) {
36847
36865
  if (v && typeof v === "object" && v.property) return v.property;
36848
36866
  }
36849
36867
  return null;
36850
36868
  }
36851
36869
  function buildPath(args) {
36852
- if (args.url) {
36853
- try {
36854
- const u = new URL(args.url);
36855
- return `${u.pathname}${u.search}`;
36856
- } catch {
36857
- return args.url.startsWith("/") ? args.url : `/${args.url}`;
36858
- }
36859
- }
36860
- if (args.zpid !== void 0) {
36861
- return `/homedetails/${args.zpid}_zpid/`;
36862
- }
36870
+ if (args.url) return urlToPath(args.url);
36871
+ if (args.zpid !== void 0) return `/homedetails/${args.zpid}_zpid/`;
36863
36872
  throw new Error("zillow_get_property: must provide either zpid or url");
36864
36873
  }
36865
36874
  function format(raw) {
@@ -36869,6 +36878,7 @@ function format(raw) {
36869
36878
  zpid,
36870
36879
  url: url2,
36871
36880
  address: raw.address,
36881
+ neighborhood: raw.address?.neighborhood,
36872
36882
  price: raw.price,
36873
36883
  zestimate: raw.zestimate,
36874
36884
  rent_zestimate: raw.rentZestimate,
@@ -36895,8 +36905,14 @@ function registerPropertyTools(server2, client2) {
36895
36905
  server2.registerTool(
36896
36906
  "zillow_get_property",
36897
36907
  {
36898
- description: "Fetch a property's full Zillow record by zpid (e.g. 12345) or by homedetails URL. Returns address, price, Zestimate, beds/baths, square footage, year built, schools, and price history. Provide exactly one of zpid or url.",
36899
- annotations: { readOnlyHint: true },
36908
+ title: "Get Zillow property details",
36909
+ description: "Fetch a property's full Zillow record by zpid (numeric Zillow Property ID, e.g. 12345) or by homedetails URL. Returns address, neighborhood, price, Zestimate, rent Zestimate, beds/baths, square footage, year built, schools, and price history. Provide exactly one of zpid or url. Read-only; safe to call repeatedly.",
36910
+ annotations: {
36911
+ title: "Get Zillow property details",
36912
+ readOnlyHint: true,
36913
+ idempotentHint: true,
36914
+ openWorldHint: true
36915
+ },
36900
36916
  inputSchema: {
36901
36917
  zpid: external_exports.union([external_exports.number().int().positive(), external_exports.string()]).optional().describe("Zillow Property ID (numeric)"),
36902
36918
  url: external_exports.string().optional().describe("A Zillow homedetails URL (or path beginning with /homedetails/)")
@@ -36949,48 +36965,76 @@ function registerZestimateTools(server2, client2) {
36949
36965
  server2.registerTool(
36950
36966
  "zillow_get_zestimate_history",
36951
36967
  {
36952
- description: "Historical Zestimate values for a property, by zpid. Returns a time series of {date, value, rent?} entries. Pulls from the same homedetails page as zillow_get_property \u2014 if you've already fetched the property, you have this data too.",
36953
- annotations: { readOnlyHint: true },
36968
+ title: "Get Zestimate history for a property",
36969
+ description: "Historical Zestimate values for a property by zpid or homedetails URL. Returns a time series of {date, value, rent?} entries (rent included when Zillow has a rent Zestimate for the property). Note: zillow_get_property returns only the *current* Zestimate as a scalar \u2014 call this tool when you need the trend. Read-only; safe to call repeatedly.",
36970
+ annotations: {
36971
+ title: "Get Zestimate history for a property",
36972
+ readOnlyHint: true,
36973
+ idempotentHint: true,
36974
+ openWorldHint: true
36975
+ },
36954
36976
  inputSchema: {
36955
- zpid: external_exports.union([external_exports.number().int().positive(), external_exports.string()]).describe("Zillow Property ID")
36977
+ zpid: external_exports.union([external_exports.number().int().positive(), external_exports.string()]).optional().describe("Zillow Property ID. Provide either zpid or url."),
36978
+ url: external_exports.string().optional().describe(
36979
+ "Zillow homedetails URL (or path). Provide either zpid or url."
36980
+ )
36956
36981
  }
36957
36982
  },
36958
- async ({ zpid }) => {
36959
- const html = await client2.fetchHtml(`/homedetails/${zpid}_zpid/`);
36983
+ async ({ zpid, url: url2 }) => {
36984
+ const path = url2 ? urlToPath(url2) : zpid !== void 0 ? `/homedetails/${zpid}_zpid/` : null;
36985
+ if (!path) {
36986
+ throw new Error(
36987
+ "zillow_get_zestimate_history: must provide either zpid or url"
36988
+ );
36989
+ }
36990
+ const html = await client2.fetchHtml(path);
36960
36991
  const nextData = extractNextData(html);
36961
36992
  const pageProps = getPageProps(nextData);
36962
36993
  const property = findPropertyInPageProps(pageProps);
36963
36994
  if (!property) {
36964
36995
  throw new Error(
36965
- `Could not locate property data in __NEXT_DATA__ for zpid=${zpid}.`
36996
+ `Could not locate property data in __NEXT_DATA__ at ${path}.`
36966
36997
  );
36967
36998
  }
36968
36999
  const series = extractZestimateHistory(property);
36969
- return textResult({ zpid: String(zpid), points: series });
37000
+ return textResult({
37001
+ zpid: String(property.zpid ?? zpid ?? ""),
37002
+ points: series
37003
+ });
36970
37004
  }
36971
37005
  );
36972
37006
  }
36973
37007
 
36974
- // src/tools/saved.ts
36975
- function findSavedSearches(pageProps) {
36976
- const direct = pageProps.savedSearches ?? pageProps.userSavedSearches;
36977
- if (Array.isArray(direct)) return direct;
37008
+ // src/page-props.ts
37009
+ function findArrayByShape(pageProps, directKeys, shapeMatches) {
37010
+ for (const key of directKeys) {
37011
+ const candidate = pageProps[key];
37012
+ if (Array.isArray(candidate)) return candidate;
37013
+ }
36978
37014
  for (const v of Object.values(pageProps)) {
36979
- if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && ("searchQueryState" in v[0] || "filterState" in v[0])) {
37015
+ if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && shapeMatches(v[0])) {
36980
37016
  return v;
36981
37017
  }
36982
37018
  }
36983
37019
  return [];
36984
37020
  }
37021
+
37022
+ // src/tools/saved.ts
37023
+ var SAVED_SEARCH_KEYS = ["savedSearches", "userSavedSearches"];
37024
+ var SAVED_HOME_KEYS = ["savedHomes", "userSavedHomes", "favoriteHomes"];
37025
+ function findSavedSearches(pageProps) {
37026
+ return findArrayByShape(
37027
+ pageProps,
37028
+ SAVED_SEARCH_KEYS,
37029
+ (el) => "searchQueryState" in el || "filterState" in el
37030
+ );
37031
+ }
36985
37032
  function findSavedHomes(pageProps) {
36986
- const direct = pageProps.savedHomes ?? pageProps.userSavedHomes ?? pageProps.favoriteHomes;
36987
- if (Array.isArray(direct)) return direct;
36988
- for (const v of Object.values(pageProps)) {
36989
- if (Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && "zpid" in v[0] && ("hdpUrl" in v[0] || "savedAt" in v[0])) {
36990
- return v;
36991
- }
36992
- }
36993
- return [];
37033
+ return findArrayByShape(
37034
+ pageProps,
37035
+ SAVED_HOME_KEYS,
37036
+ (el) => "zpid" in el && ("hdpUrl" in el || "savedAt" in el)
37037
+ );
36994
37038
  }
36995
37039
  function formatSearch(raw) {
36996
37040
  return {
@@ -37025,8 +37069,14 @@ function registerSavedTools(server2, client2) {
37025
37069
  server2.registerTool(
37026
37070
  "zillow_get_saved_searches",
37027
37071
  {
37028
- description: "The signed-in user's saved searches (name, filters, new-listing count, notification frequency). Requires being signed in to zillow.com in the bridged browser tab.",
37029
- annotations: { readOnlyHint: true },
37072
+ title: "Get my saved Zillow searches",
37073
+ description: "The signed-in user's saved searches on zillow.com (name, filters, new-listing count, notification frequency). Requires the user to be signed in at zillow.com in the bridged browser tab \u2014 throws SessionNotAuthenticatedError otherwise. Read-only; safe to call repeatedly.",
37074
+ annotations: {
37075
+ title: "Get my saved Zillow searches",
37076
+ readOnlyHint: true,
37077
+ idempotentHint: true,
37078
+ openWorldHint: true
37079
+ },
37030
37080
  inputSchema: {}
37031
37081
  },
37032
37082
  async () => {
@@ -37040,8 +37090,14 @@ function registerSavedTools(server2, client2) {
37040
37090
  server2.registerTool(
37041
37091
  "zillow_get_saved_homes",
37042
37092
  {
37043
- description: "The signed-in user's saved (favorited) homes. Returns address, price, Zestimate, status, and when the home was saved. Requires being signed in.",
37044
- annotations: { readOnlyHint: true },
37093
+ 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.",
37095
+ annotations: {
37096
+ title: "Get my saved (favorited) Zillow homes",
37097
+ readOnlyHint: true,
37098
+ idempotentHint: true,
37099
+ openWorldHint: true
37100
+ },
37045
37101
  inputSchema: {}
37046
37102
  },
37047
37103
  async () => {
@@ -37087,17 +37143,10 @@ function format2(raw) {
37087
37143
  };
37088
37144
  }
37089
37145
  function pathFromInput(args) {
37090
- if (args.url) {
37091
- try {
37092
- const u = new URL(args.url);
37093
- return `${u.pathname}${u.search}`;
37094
- } catch {
37095
- return args.url.startsWith("/") ? args.url : `/${args.url}`;
37096
- }
37097
- }
37146
+ if (args.url) return urlToPath(args.url);
37098
37147
  if (args.region_path) {
37099
- const p = args.region_path.startsWith("/") ? args.region_path : `/${args.region_path}`;
37100
- return p.includes("/home-values/") ? p : `/home-values${p.startsWith("/") ? p : `/${p}`}`;
37148
+ const p = urlToPath(args.region_path);
37149
+ return p.includes("/home-values/") ? p : `/home-values${p}`;
37101
37150
  }
37102
37151
  throw new Error(
37103
37152
  'zillow_get_market_report: provide either region_path (e.g. "/home-values/6181/brooklyn-ny/") or url.'
@@ -37107,8 +37156,14 @@ function registerMarketTools(server2, client2) {
37107
37156
  server2.registerTool(
37108
37157
  "zillow_get_market_report",
37109
37158
  {
37110
- description: 'Market report for a Zillow region: median sale/list/rent prices, days on market, inventory, Zillow Home Value Index (ZHVI). Provide either a region_path (e.g. "/home-values/6181/brooklyn-ny/") or a full Zillow home-values URL.',
37111
- annotations: { readOnlyHint: true },
37159
+ 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.',
37161
+ annotations: {
37162
+ title: "Get Zillow market report for a region",
37163
+ readOnlyHint: true,
37164
+ idempotentHint: true,
37165
+ openWorldHint: true
37166
+ },
37112
37167
  inputSchema: {
37113
37168
  region_path: external_exports.string().optional().describe(
37114
37169
  'Path under /home-values/, e.g. "/home-values/6181/brooklyn-ny/" or "6181/brooklyn-ny/"'
@@ -37185,8 +37240,14 @@ function registerMortgageTools(server2) {
37185
37240
  server2.registerTool(
37186
37241
  "zillow_calculate_mortgage",
37187
37242
  {
37188
- description: "Local-only mortgage payment calculator. Returns a full PITI breakdown (principal + interest, property tax, insurance, HOA, PMI) and total interest over the life of the loan. No network call. Provide either down_payment OR down_payment_percent; defaults to 20%. Property tax can be given as property_tax_annual or property_tax_rate (% of home price). PMI applies automatically when LTV > 80% and pmi_rate is provided.",
37189
- annotations: { readOnlyHint: true },
37243
+ title: "Calculate mortgage payment (local)",
37244
+ description: "Local-only mortgage payment calculator. Returns a full PITI breakdown (principal + interest, property tax, insurance, HOA, PMI) and total interest over the life of the loan. No network call \u2014 fully deterministic, safe to use for scenario comparison without burning a fetch. Provide either down_payment OR down_payment_percent; defaults to 20%. Property tax can be given as property_tax_annual or property_tax_rate (% of home price). PMI applies automatically when LTV > 80% and pmi_rate is provided.",
37245
+ annotations: {
37246
+ title: "Calculate mortgage payment (local)",
37247
+ readOnlyHint: true,
37248
+ idempotentHint: true,
37249
+ openWorldHint: false
37250
+ },
37190
37251
  inputSchema: {
37191
37252
  home_price: external_exports.number().positive(),
37192
37253
  down_payment: external_exports.number().nonnegative().optional(),
@@ -37205,7 +37266,7 @@ function registerMortgageTools(server2) {
37205
37266
  }
37206
37267
 
37207
37268
  // src/index.ts
37208
- var VERSION = "0.1.0";
37269
+ var VERSION = "0.2.1";
37209
37270
  var port = process.env.ZILLOW_WS_PORT ? Number(process.env.ZILLOW_WS_PORT) : void 0;
37210
37271
  var transport = new FetchproxyTransport({ port, version: VERSION });
37211
37272
  var client = new ZillowClient({ transport });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zillow-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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.1.0",
9
+ "version": "0.2.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "zillow-mcp",
14
- "version": "0.1.0",
14
+ "version": "0.2.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },