@sfranalytics/mcp 0.6.0

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.
Files changed (198) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +147 -0
  3. package/dist/config.d.ts +28 -0
  4. package/dist/config.js +101 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/http.d.ts +2 -0
  7. package/dist/http.js +252 -0
  8. package/dist/http.js.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +41 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/server.d.ts +3 -0
  13. package/dist/server.js +213 -0
  14. package/dist/server.js.map +1 -0
  15. package/dist/services/httpClient.d.ts +19 -0
  16. package/dist/services/httpClient.js +73 -0
  17. package/dist/services/httpClient.js.map +1 -0
  18. package/dist/services/plr.d.ts +26 -0
  19. package/dist/services/plr.js +75 -0
  20. package/dist/services/plr.js.map +1 -0
  21. package/dist/services/sfr.d.ts +33 -0
  22. package/dist/services/sfr.js +124 -0
  23. package/dist/services/sfr.js.map +1 -0
  24. package/dist/services/snowflake.d.ts +12 -0
  25. package/dist/services/snowflake.js +71 -0
  26. package/dist/services/snowflake.js.map +1 -0
  27. package/dist/tools/dateHelper.d.ts +26 -0
  28. package/dist/tools/dateHelper.js +86 -0
  29. package/dist/tools/dateHelper.js.map +1 -0
  30. package/dist/tools/formatters.d.ts +66 -0
  31. package/dist/tools/formatters.js +219 -0
  32. package/dist/tools/formatters.js.map +1 -0
  33. package/dist/tools/health.d.ts +5 -0
  34. package/dist/tools/health.js +38 -0
  35. package/dist/tools/health.js.map +1 -0
  36. package/dist/tools/nextActions.d.ts +10 -0
  37. package/dist/tools/nextActions.js +23 -0
  38. package/dist/tools/nextActions.js.map +1 -0
  39. package/dist/tools/plr/borrowerContacts.d.ts +3 -0
  40. package/dist/tools/plr/borrowerContacts.js +73 -0
  41. package/dist/tools/plr/borrowerContacts.js.map +1 -0
  42. package/dist/tools/plr/borrowerLoans.d.ts +3 -0
  43. package/dist/tools/plr/borrowerLoans.js +115 -0
  44. package/dist/tools/plr/borrowerLoans.js.map +1 -0
  45. package/dist/tools/plr/borrowerProfile.d.ts +3 -0
  46. package/dist/tools/plr/borrowerProfile.js +201 -0
  47. package/dist/tools/plr/borrowerProfile.js.map +1 -0
  48. package/dist/tools/plr/borrowerRankings.d.ts +3 -0
  49. package/dist/tools/plr/borrowerRankings.js +123 -0
  50. package/dist/tools/plr/borrowerRankings.js.map +1 -0
  51. package/dist/tools/plr/borrowerSearch.d.ts +3 -0
  52. package/dist/tools/plr/borrowerSearch.js +128 -0
  53. package/dist/tools/plr/borrowerSearch.js.map +1 -0
  54. package/dist/tools/plr/churnedBorrowers.d.ts +3 -0
  55. package/dist/tools/plr/churnedBorrowers.js +86 -0
  56. package/dist/tools/plr/churnedBorrowers.js.map +1 -0
  57. package/dist/tools/plr/lenderBorrowers.d.ts +3 -0
  58. package/dist/tools/plr/lenderBorrowers.js +71 -0
  59. package/dist/tools/plr/lenderBorrowers.js.map +1 -0
  60. package/dist/tools/plr/lenderRankings.d.ts +3 -0
  61. package/dist/tools/plr/lenderRankings.js +74 -0
  62. package/dist/tools/plr/lenderRankings.js.map +1 -0
  63. package/dist/tools/plr/loansNearby.d.ts +3 -0
  64. package/dist/tools/plr/loansNearby.js +113 -0
  65. package/dist/tools/plr/loansNearby.js.map +1 -0
  66. package/dist/tools/plr/marketTrends.d.ts +3 -0
  67. package/dist/tools/plr/marketTrends.js +95 -0
  68. package/dist/tools/plr/marketTrends.js.map +1 -0
  69. package/dist/tools/plr/msaRankings.d.ts +3 -0
  70. package/dist/tools/plr/msaRankings.js +74 -0
  71. package/dist/tools/plr/msaRankings.js.map +1 -0
  72. package/dist/tools/plr/negativeRemarks.d.ts +3 -0
  73. package/dist/tools/plr/negativeRemarks.js +94 -0
  74. package/dist/tools/plr/negativeRemarks.js.map +1 -0
  75. package/dist/tools/plr/ownerSearch.d.ts +3 -0
  76. package/dist/tools/plr/ownerSearch.js +57 -0
  77. package/dist/tools/plr/ownerSearch.js.map +1 -0
  78. package/dist/tools/plr/portfolioSummary.d.ts +3 -0
  79. package/dist/tools/plr/portfolioSummary.js +99 -0
  80. package/dist/tools/plr/portfolioSummary.js.map +1 -0
  81. package/dist/tools/plr/topBorrowers.d.ts +3 -0
  82. package/dist/tools/plr/topBorrowers.js +69 -0
  83. package/dist/tools/plr/topBorrowers.js.map +1 -0
  84. package/dist/tools/plr/topLenders.d.ts +3 -0
  85. package/dist/tools/plr/topLenders.js +75 -0
  86. package/dist/tools/plr/topLenders.js.map +1 -0
  87. package/dist/tools/plr/transactionHistory.d.ts +3 -0
  88. package/dist/tools/plr/transactionHistory.js +74 -0
  89. package/dist/tools/plr/transactionHistory.js.map +1 -0
  90. package/dist/tools/prompts.d.ts +7 -0
  91. package/dist/tools/prompts.js +157 -0
  92. package/dist/tools/prompts.js.map +1 -0
  93. package/dist/tools/registerToolSafe.d.ts +29 -0
  94. package/dist/tools/registerToolSafe.js +36 -0
  95. package/dist/tools/registerToolSafe.js.map +1 -0
  96. package/dist/tools/sfr/activityHighlights.d.ts +3 -0
  97. package/dist/tools/sfr/activityHighlights.js +70 -0
  98. package/dist/tools/sfr/activityHighlights.js.map +1 -0
  99. package/dist/tools/sfr/bestBuyers.d.ts +3 -0
  100. package/dist/tools/sfr/bestBuyers.js +60 -0
  101. package/dist/tools/sfr/bestBuyers.js.map +1 -0
  102. package/dist/tools/sfr/buyerGrowth.d.ts +3 -0
  103. package/dist/tools/sfr/buyerGrowth.js +68 -0
  104. package/dist/tools/sfr/buyerGrowth.js.map +1 -0
  105. package/dist/tools/sfr/buyerProfile.d.ts +3 -0
  106. package/dist/tools/sfr/buyerProfile.js +162 -0
  107. package/dist/tools/sfr/buyerProfile.js.map +1 -0
  108. package/dist/tools/sfr/distressSearch.d.ts +3 -0
  109. package/dist/tools/sfr/distressSearch.js +173 -0
  110. package/dist/tools/sfr/distressSearch.js.map +1 -0
  111. package/dist/tools/sfr/flipActivity.d.ts +3 -0
  112. package/dist/tools/sfr/flipActivity.js +110 -0
  113. package/dist/tools/sfr/flipActivity.js.map +1 -0
  114. package/dist/tools/sfr/flipStats.d.ts +3 -0
  115. package/dist/tools/sfr/flipStats.js +98 -0
  116. package/dist/tools/sfr/flipStats.js.map +1 -0
  117. package/dist/tools/sfr/getProperty.d.ts +3 -0
  118. package/dist/tools/sfr/getProperty.js +142 -0
  119. package/dist/tools/sfr/getProperty.js.map +1 -0
  120. package/dist/tools/sfr/institutionalOwners.d.ts +3 -0
  121. package/dist/tools/sfr/institutionalOwners.js +88 -0
  122. package/dist/tools/sfr/institutionalOwners.js.map +1 -0
  123. package/dist/tools/sfr/investorActivity.d.ts +3 -0
  124. package/dist/tools/sfr/investorActivity.js +130 -0
  125. package/dist/tools/sfr/investorActivity.js.map +1 -0
  126. package/dist/tools/sfr/marketHighlights.d.ts +3 -0
  127. package/dist/tools/sfr/marketHighlights.js +100 -0
  128. package/dist/tools/sfr/marketHighlights.js.map +1 -0
  129. package/dist/tools/sfr/msaResolver.d.ts +15 -0
  130. package/dist/tools/sfr/msaResolver.js +109 -0
  131. package/dist/tools/sfr/msaResolver.js.map +1 -0
  132. package/dist/tools/sfr/propertyBatch.d.ts +3 -0
  133. package/dist/tools/sfr/propertyBatch.js +73 -0
  134. package/dist/tools/sfr/propertyBatch.js.map +1 -0
  135. package/dist/tools/sfr/propertyComps.d.ts +3 -0
  136. package/dist/tools/sfr/propertyComps.js +56 -0
  137. package/dist/tools/sfr/propertyComps.js.map +1 -0
  138. package/dist/tools/sfr/propertyTransactions.d.ts +3 -0
  139. package/dist/tools/sfr/propertyTransactions.js +50 -0
  140. package/dist/tools/sfr/propertyTransactions.js.map +1 -0
  141. package/dist/tools/sfr/rentalComparables.d.ts +3 -0
  142. package/dist/tools/sfr/rentalComparables.js +91 -0
  143. package/dist/tools/sfr/rentalComparables.js.map +1 -0
  144. package/dist/tools/sfr/rentalMarketAnalysis.d.ts +3 -0
  145. package/dist/tools/sfr/rentalMarketAnalysis.js +134 -0
  146. package/dist/tools/sfr/rentalMarketAnalysis.js.map +1 -0
  147. package/dist/tools/sfr/rentalStats.d.ts +3 -0
  148. package/dist/tools/sfr/rentalStats.js +118 -0
  149. package/dist/tools/sfr/rentalStats.js.map +1 -0
  150. package/dist/tools/sfr/searchProperties.d.ts +3 -0
  151. package/dist/tools/sfr/searchProperties.js +157 -0
  152. package/dist/tools/sfr/searchProperties.js.map +1 -0
  153. package/dist/tools/sfr/topBuyers.d.ts +3 -0
  154. package/dist/tools/sfr/topBuyers.js +91 -0
  155. package/dist/tools/sfr/topBuyers.js.map +1 -0
  156. package/dist/tools/sfr/zipDetail.d.ts +3 -0
  157. package/dist/tools/sfr/zipDetail.js +85 -0
  158. package/dist/tools/sfr/zipDetail.js.map +1 -0
  159. package/dist/tools/sfr/zipFinder.d.ts +3 -0
  160. package/dist/tools/sfr/zipFinder.js +79 -0
  161. package/dist/tools/sfr/zipFinder.js.map +1 -0
  162. package/dist/tools/slugHelper.d.ts +2 -0
  163. package/dist/tools/slugHelper.js +5 -0
  164. package/dist/tools/slugHelper.js.map +1 -0
  165. package/dist/tools/snowflake/compareMarkets.d.ts +2 -0
  166. package/dist/tools/snowflake/compareMarkets.js +95 -0
  167. package/dist/tools/snowflake/compareMarkets.js.map +1 -0
  168. package/dist/tools/snowflake/hviTrend.d.ts +2 -0
  169. package/dist/tools/snowflake/hviTrend.js +77 -0
  170. package/dist/tools/snowflake/hviTrend.js.map +1 -0
  171. package/dist/tools/snowflake/investorActivity.d.ts +2 -0
  172. package/dist/tools/snowflake/investorActivity.js +138 -0
  173. package/dist/tools/snowflake/investorActivity.js.map +1 -0
  174. package/dist/tools/snowflake/marketSnapshot.d.ts +2 -0
  175. package/dist/tools/snowflake/marketSnapshot.js +185 -0
  176. package/dist/tools/snowflake/marketSnapshot.js.map +1 -0
  177. package/dist/tools/snowflake/marketTrends.d.ts +2 -0
  178. package/dist/tools/snowflake/marketTrends.js +81 -0
  179. package/dist/tools/snowflake/marketTrends.js.map +1 -0
  180. package/dist/tools/snowflake/rankRentalZips.d.ts +2 -0
  181. package/dist/tools/snowflake/rankRentalZips.js +94 -0
  182. package/dist/tools/snowflake/rankRentalZips.js.map +1 -0
  183. package/dist/tools/snowflake/rankZips.d.ts +2 -0
  184. package/dist/tools/snowflake/rankZips.js +86 -0
  185. package/dist/tools/snowflake/rankZips.js.map +1 -0
  186. package/dist/tools/snowflake/rentalMarket.d.ts +2 -0
  187. package/dist/tools/snowflake/rentalMarket.js +111 -0
  188. package/dist/tools/snowflake/rentalMarket.js.map +1 -0
  189. package/dist/tools/snowflake/rentalYield.d.ts +2 -0
  190. package/dist/tools/snowflake/rentalYield.js +143 -0
  191. package/dist/tools/snowflake/rentalYield.js.map +1 -0
  192. package/dist/tools/snowflake/zipProfile.d.ts +2 -0
  193. package/dist/tools/snowflake/zipProfile.js +110 -0
  194. package/dist/tools/snowflake/zipProfile.js.map +1 -0
  195. package/dist/version.d.ts +1 -0
  196. package/dist/version.js +5 -0
  197. package/dist/version.js.map +1 -0
  198. package/package.json +53 -0
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import { sfQuery } from "../../services/snowflake.js";
3
+ import { structuredResult, markdownTable } from "../formatters.js";
4
+ import { action } from "../nextActions.js";
5
+ import { registerToolSafe } from "../registerToolSafe.js";
6
+ const PROPERTY_TYPES = [
7
+ "All Residential", "Single Family Residential", "Condo/Co-op",
8
+ "Townhouse", "Multi-Family (2-4 Unit)",
9
+ ];
10
+ const Input = z.object({
11
+ zipCode: z.string().describe("5-digit ZIP code"),
12
+ state: z.string().describe("2-letter state code"),
13
+ months: z.coerce.number().optional().describe("Months of history (default 12, max 60)"),
14
+ propertyType: z.enum(PROPERTY_TYPES).optional().describe("Property type filter"),
15
+ });
16
+ function fmt$(v) {
17
+ if (v === null)
18
+ return "—";
19
+ return "$" + Math.round(v).toLocaleString("en-US");
20
+ }
21
+ export function registerMarketTrendsTool(server) {
22
+ registerToolSafe(server, "sfr_market_trends", {
23
+ title: "Redfin price/DOM/inventory trend for a ZIP (Snowflake)",
24
+ description: "Get historical Redfin market data for a ZIP code: median sale price, days on market, " +
25
+ "and inventory over time. Detects appreciation/depreciation/flat trend. Up to 60 months.",
26
+ inputSchema: Input,
27
+ }, async (args) => {
28
+ const st = args.state.toUpperCase();
29
+ const pt = args.propertyType ?? "All Residential";
30
+ const months = Math.min(args.months ?? 12, 60);
31
+ const rows = await sfQuery(`SELECT PERIOD_END, MEDIAN_SALE_PRICE, MEDIAN_DOM, INVENTORY
32
+ FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET
33
+ WHERE REGION = ? AND STATE_CODE = ? AND PROPERTY_TYPE = ?
34
+ AND PERIOD_END >= DATEADD(month, -?, CURRENT_DATE) ORDER BY PERIOD_END ASC`, [args.zipCode, st, pt, months]);
35
+ if (rows.length === 0) {
36
+ return structuredResult(`## Market Trends — ${args.zipCode}, ${st}\n\n_No data found._`, {});
37
+ }
38
+ const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
39
+ const first = rows[0].MEDIAN_SALE_PRICE;
40
+ const last = rows[rows.length - 1].MEDIAN_SALE_PRICE;
41
+ let direction = "flat";
42
+ let changePct = null;
43
+ if (first !== null && last !== null && first > 0) {
44
+ changePct = (last - first) / first;
45
+ if (changePct > 0.02)
46
+ direction = "appreciating";
47
+ else if (changePct < -0.02)
48
+ direction = "depreciating";
49
+ }
50
+ const tableRows = rows.map((r) => {
51
+ const d = new Date(r.PERIOD_END);
52
+ return [
53
+ `${monthNames[d.getMonth()]} ${String(d.getFullYear()).slice(2)}`,
54
+ fmt$(r.MEDIAN_SALE_PRICE),
55
+ r.MEDIAN_DOM !== null ? `${r.MEDIAN_DOM}d` : "—",
56
+ r.INVENTORY !== null ? r.INVENTORY.toLocaleString() : "—",
57
+ ];
58
+ });
59
+ const changeLbl = changePct !== null
60
+ ? `${changePct >= 0 ? "+" : ""}${(changePct * 100).toFixed(1)}%`
61
+ : "N/A";
62
+ const lines = [
63
+ `## Market Trends — ${args.zipCode}, ${st} (${months} months)`,
64
+ "",
65
+ `**Trend:** ${direction.toUpperCase()} (${changeLbl})`,
66
+ `**Start:** ${fmt$(first)} | **Latest:** ${fmt$(last)}`,
67
+ "",
68
+ markdownTable(["Date", "Med. Price", "DOM", "Inventory"], tableRows),
69
+ ];
70
+ const actions = [
71
+ action("sfr_market_snapshot", "Current snapshot", { zipCode: args.zipCode, state: st }),
72
+ action("sfr_hvi_trend", "Home Value Index trend", { zipCode: args.zipCode }),
73
+ action("sfr_rank_zips", "Rank ZIPs by growth", { state: st, sortBy: "price_growth", metro: "" }),
74
+ ];
75
+ return structuredResult(lines.join("\n"), {
76
+ zipCode: args.zipCode, state: st, months, direction, changePct,
77
+ startPrice: first, endPrice: last, dataPoints: rows.length,
78
+ }, actions);
79
+ });
80
+ }
81
+ //# sourceMappingURL=marketTrends.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marketTrends.js","sourceRoot":"","sources":["../../../src/tools/snowflake/marketTrends.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRankRentalZipsTool(server: McpServer): void;
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ import { sfQuery } from "../../services/snowflake.js";
3
+ import { structuredResult, markdownTable } from "../formatters.js";
4
+ import { action } from "../nextActions.js";
5
+ import { registerToolSafe } from "../registerToolSafe.js";
6
+ const RENT_TABLE = "SFRA_PROD.PUBLIC.RENT_VIEW";
7
+ const SORT_OPTIONS = ["median_rent", "gross_yield", "rent_per_sqft", "total_listings"];
8
+ const SORT_COLUMN = {
9
+ median_rent: "MEDIAN_RENT",
10
+ gross_yield: "MARKET_YIELD",
11
+ rent_per_sqft: "AVG_RENT_PER_SQFT",
12
+ total_listings: "TOTAL_LISTINGS",
13
+ };
14
+ const Input = z.object({
15
+ state: z.string().describe("2-letter state code"),
16
+ metro: z.string().optional().describe("Metro/MSA name filter (fuzzy)"),
17
+ sortBy: z.enum(SORT_OPTIONS).describe("Metric to rank by"),
18
+ order: z.enum(["asc", "desc"]).optional().describe("Sort order (default: desc)"),
19
+ limit: z.coerce.number().optional().describe("Max results (default 15, max 50)"),
20
+ bedrooms: z.coerce.number().optional().describe("Filter by bedroom count"),
21
+ propertyType: z.string().optional().describe("Filter by home type"),
22
+ minListings: z.coerce.number().optional().describe("Minimum listings per ZIP (default 10)"),
23
+ });
24
+ function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
25
+ function fmtPct(v, d = 1) { return v !== null ? (v * 100).toFixed(d) + "%" : "—"; }
26
+ export function registerRankRentalZipsTool(server) {
27
+ registerToolSafe(server, "sfr_rank_rental_zips", {
28
+ title: "Rank ZIPs by rental metrics (Snowflake)",
29
+ description: "Rank ZIP codes by median rent, gross yield, rent/sqft, or listing count. " +
30
+ "Uses RENT_VIEW (5.9M listings) for real rental data with market yield calculation.",
31
+ inputSchema: Input,
32
+ }, async (args) => {
33
+ const st = args.state.toUpperCase();
34
+ const limit = Math.min(args.limit ?? 15, 50);
35
+ const order = args.order ?? "desc";
36
+ const col = SORT_COLUMN[args.sortBy];
37
+ const minList = args.minListings ?? 10;
38
+ const clauses = [
39
+ "r.LOCATION_STATE_ABBR = ?", "r.PRICE IS NOT NULL", "r.PRICE > 0", "r.PRICE < 25000",
40
+ "r.EFFECTIVE_DATE >= DATEADD(month, -12, CURRENT_DATE)", "r.ADDRESS_ZIPCODE IS NOT NULL",
41
+ ];
42
+ const binds = [st];
43
+ if (args.metro) {
44
+ clauses.push("UPPER(r.LOCATION_MSA) LIKE UPPER(?)");
45
+ binds.push(`%${args.metro}%`);
46
+ }
47
+ if (args.bedrooms !== undefined) {
48
+ clauses.push("r.BEDROOMS = ?");
49
+ binds.push(args.bedrooms);
50
+ }
51
+ if (args.propertyType) {
52
+ clauses.push("UPPER(r.HOMETYPE) = UPPER(?)");
53
+ binds.push(args.propertyType);
54
+ }
55
+ const rows = await sfQuery(`SELECT r.ADDRESS_ZIPCODE AS ZIP, MAX(r.LOCATION_CITY) AS CITY, COUNT(*) AS TOTAL_LISTINGS,
56
+ MEDIAN(r.PRICE) AS MEDIAN_RENT, AVG(r.PRICE) AS AVG_RENT,
57
+ AVG(CASE WHEN r.LIVINGAREA > 0 THEN r.PRICE / r.LIVINGAREA ELSE NULL END) AS AVG_RENT_PER_SQFT,
58
+ CASE WHEN MEDIAN(COALESCE(r.CURRENT_AVM_VALUE, r.ZESTIMATE)) > 0
59
+ THEN (MEDIAN(r.PRICE) * 12) / MEDIAN(COALESCE(r.CURRENT_AVM_VALUE, r.ZESTIMATE))
60
+ ELSE NULL END AS MARKET_YIELD,
61
+ MEDIAN(COALESCE(r.CURRENT_AVM_VALUE, r.ZESTIMATE)) AS MEDIAN_AVM
62
+ FROM ${RENT_TABLE} r WHERE ${clauses.join(" AND ")}
63
+ GROUP BY r.ADDRESS_ZIPCODE HAVING COUNT(*) >= ?
64
+ ORDER BY ${col} ${order === "asc" ? "ASC" : "DESC"} NULLS LAST LIMIT ?`, [...binds, minList, limit]);
65
+ if (rows.length === 0) {
66
+ return structuredResult(`## Rental ZIP Rankings\n\n_No data for ${st}${args.metro ? ` (${args.metro})` : ""}_`, {});
67
+ }
68
+ const tableRows = rows.map((r, i) => [
69
+ i + 1, r.ZIP, (r.CITY ?? "").slice(0, 16), fmt$(r.MEDIAN_RENT),
70
+ r.TOTAL_LISTINGS.toLocaleString(),
71
+ r.MARKET_YIELD !== null ? fmtPct(r.MARKET_YIELD) : "—",
72
+ fmt$(r.MEDIAN_AVM),
73
+ ]);
74
+ const metroLbl = args.metro ? ` (${args.metro})` : "";
75
+ const lines = [
76
+ `## Top ${rows.length} Rental ZIPs — ${st}${metroLbl} (by ${args.sortBy}, ${order})`,
77
+ "",
78
+ markdownTable(["#", "ZIP", "City", "Med. Rent", "Listings", "Mkt Yield", "Home Value"], tableRows),
79
+ ];
80
+ const topZip = rows[0].ZIP;
81
+ const actions = [
82
+ action("sfr_rental_yield", `Yield for ${topZip}`, { zipCode: topZip, state: st }),
83
+ action("sfr_rental_market_sf", `Rental stats for ${topZip}`, { zipCode: topZip, state: st }),
84
+ ];
85
+ return structuredResult(lines.join("\n"), {
86
+ state: st, sortBy: args.sortBy, count: rows.length,
87
+ zips: rows.map((r) => ({
88
+ zip: r.ZIP, city: r.CITY, medianRent: r.MEDIAN_RENT,
89
+ listings: r.TOTAL_LISTINGS, yield: r.MARKET_YIELD, homeValue: r.MEDIAN_AVM,
90
+ })),
91
+ }, actions);
92
+ });
93
+ }
94
+ //# sourceMappingURL=rankRentalZips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rankRentalZips.js","sourceRoot":"","sources":["../../../src/tools/snowflake/rankRentalZips.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRankZipsTool(server: McpServer): void;
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ import { sfQuery } from "../../services/snowflake.js";
3
+ import { structuredResult, markdownTable } from "../formatters.js";
4
+ import { action } from "../nextActions.js";
5
+ import { registerToolSafe } from "../registerToolSafe.js";
6
+ const PROPERTY_TYPES = [
7
+ "All Residential", "Single Family Residential", "Condo/Co-op",
8
+ "Townhouse", "Multi-Family (2-4 Unit)",
9
+ ];
10
+ const SORT_OPTIONS = [
11
+ "median_price", "dom", "inventory", "price_growth", "homes_sold", "sale_to_list",
12
+ ];
13
+ const SORT_COLUMN = {
14
+ median_price: "MEDIAN_SALE_PRICE",
15
+ dom: "MEDIAN_DOM",
16
+ inventory: "INVENTORY",
17
+ price_growth: "MEDIAN_SALE_PRICE_YOY",
18
+ homes_sold: "HOMES_SOLD",
19
+ sale_to_list: "AVG_SALE_TO_LIST",
20
+ };
21
+ const ZIP_COLUMNS = `
22
+ REGION, CITY, PARENT_METRO_REGION, MEDIAN_SALE_PRICE,
23
+ MEDIAN_DOM, INVENTORY, HOMES_SOLD, NEW_LISTINGS,
24
+ AVG_SALE_TO_LIST, SOLD_ABOVE_LIST, MEDIAN_SALE_PRICE_YOY, PRICE_DROPS`;
25
+ const Input = z.object({
26
+ metro: z.string().describe("Metro area name (fuzzy match), e.g. 'Austin' or 'Dallas'"),
27
+ state: z.string().describe("2-letter state code"),
28
+ sortBy: z.enum(SORT_OPTIONS).describe("Metric to sort by"),
29
+ order: z.enum(["asc", "desc"]).optional().describe("Sort order (default: desc)"),
30
+ limit: z.coerce.number().optional().describe("Max results (default 10, max 50)"),
31
+ propertyType: z.enum(PROPERTY_TYPES).optional(),
32
+ });
33
+ function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
34
+ function fmtPct(v, d = 1) { return v !== null ? (v * 100).toFixed(d) + "%" : "—"; }
35
+ export function registerRankZipsTool(server) {
36
+ registerToolSafe(server, "sfr_rank_zips", {
37
+ title: "Rank ZIP codes by Redfin metrics (Snowflake)",
38
+ description: "Rank ZIPs in a metro area by median price, DOM, inventory, price growth, " +
39
+ "homes sold, or sale-to-list ratio. Uses latest Redfin data from Snowflake.",
40
+ inputSchema: Input,
41
+ }, async (args) => {
42
+ const st = args.state.toUpperCase();
43
+ const pt = args.propertyType ?? "All Residential";
44
+ const limit = Math.min(args.limit ?? 10, 50);
45
+ const order = args.order ?? "desc";
46
+ const col = SORT_COLUMN[args.sortBy];
47
+ const rows = await sfQuery(`SELECT ${ZIP_COLUMNS} FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET
48
+ WHERE UPPER(PARENT_METRO_REGION) LIKE UPPER(?)
49
+ AND STATE_CODE = ? AND PROPERTY_TYPE = ?
50
+ AND PERIOD_END = (SELECT MAX(PERIOD_END) FROM ANALYTICS.PUBLIC.REDFIN_ZIP_MARKET WHERE STATE_CODE = ?)
51
+ ORDER BY ${col} ${order === "asc" ? "ASC" : "DESC"} NULLS LAST
52
+ LIMIT ?`, [`%${args.metro}%`, st, pt, st, limit]);
53
+ if (rows.length === 0) {
54
+ return structuredResult(`## ZIP Rankings\n\n_No data for "${args.metro}" in ${st}_`, {});
55
+ }
56
+ const tableRows = rows.map((r, i) => [
57
+ i + 1,
58
+ r.REGION,
59
+ (r.CITY ?? "").slice(0, 16),
60
+ fmt$(r.MEDIAN_SALE_PRICE),
61
+ r.MEDIAN_DOM !== null ? `${r.MEDIAN_DOM}d` : "—",
62
+ r.INVENTORY?.toLocaleString() ?? "—",
63
+ fmtPct(r.MEDIAN_SALE_PRICE_YOY),
64
+ fmtPct(r.AVG_SALE_TO_LIST, 2),
65
+ ]);
66
+ const lines = [
67
+ `## Top ${rows.length} ZIPs — ${args.metro}, ${st} (by ${args.sortBy}, ${order})`,
68
+ "",
69
+ markdownTable(["#", "ZIP", "City", "Med. Price", "DOM", "Inventory", "Price YoY", "Sale/List"], tableRows),
70
+ ];
71
+ const topZip = rows[0].REGION;
72
+ const actions = [
73
+ action("sfr_market_snapshot", `Snapshot for top ZIP ${topZip}`, { zipCode: topZip, state: st }),
74
+ action("sfr_rental_yield", `Yield for ${topZip}`, { zipCode: topZip, state: st }),
75
+ action("sfr_zip_profile", `Demographics for ${topZip}`, { zipCode: topZip }),
76
+ ];
77
+ return structuredResult(lines.join("\n"), {
78
+ metro: args.metro, state: st, sortBy: args.sortBy, order, count: rows.length,
79
+ zips: rows.map((r) => ({
80
+ zip: r.REGION, city: r.CITY, medianPrice: r.MEDIAN_SALE_PRICE,
81
+ dom: r.MEDIAN_DOM, inventory: r.INVENTORY, priceYoy: r.MEDIAN_SALE_PRICE_YOY,
82
+ })),
83
+ }, actions);
84
+ });
85
+ }
86
+ //# sourceMappingURL=rankZips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rankZips.js","sourceRoot":"","sources":["../../../src/tools/snowflake/rankZips.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,MAAM,cAAc,GAAG;IACrB,iBAAiB,EAAE,2BAA2B,EAAE,aAAa;IAC7D,WAAW,EAAE,yBAAyB;CAC9B,CAAC;AAEX,MAAM,YAAY,GAAG;IACnB,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc;CACxE,CAAC;AAEX,MAAM,WAAW,GAA2B;IAC1C,YAAY,EAAE,mBAAmB;IACjC,GAAG,EAAE,YAAY;IACjB,SAAS,EAAE,WAAW;IACtB,YAAY,EAAE,uBAAuB;IACrC,UAAU,EAAE,YAAY;IACxB,YAAY,EAAE,kBAAkB;CACjC,CAAC;AAEF,MAAM,WAAW,GAAG;;;wEAGoD,CAAC;AAEzE,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;IACtF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IACjD,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IAC1D,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IAChF,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;IAChF,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;CAChD,CAAC,CAAC;AAEH,SAAS,IAAI,CAAC,CAAgB,IAAY,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3G,SAAS,MAAM,CAAC,CAAgB,EAAE,CAAC,GAAG,CAAC,IAAY,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1G,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE;QACxC,KAAK,EAAE,8CAA8C;QACrD,WAAW,EACT,2EAA2E;YAC3E,4EAA4E;QAC9E,WAAW,EAAE,KAAK;KACnB,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAChB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,IAAI,iBAAiB,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;QACnC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB,UAAU,WAAW;;;;kBAIT,GAAG,IAAI,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;eAC1C,EACT,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CACvC,CAAC;QAEF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,gBAAgB,CAAC,oCAAoC,IAAI,CAAC,KAAK,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC;YAChD,CAAC,GAAG,CAAC;YACL,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC;YACzB,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,GAAG;YAChD,CAAC,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,GAAG;YACpC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;YAC/B,MAAM,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC;SAC9B,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG;YACZ,UAAU,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,KAAK,KAAK,EAAE,QAAQ,IAAI,CAAC,MAAM,KAAK,KAAK,GAAG;YACjF,EAAE;YACF,aAAa,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,SAAS,CAAC;SAC3G,CAAC;QAEF,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAC9B,MAAM,OAAO,GAAG;YACd,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YAC/F,MAAM,CAAC,kBAAkB,EAAE,aAAa,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;YACjF,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SAC7E,CAAC;QAEF,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACxC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM;YAC5E,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBAC1B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,iBAAiB;gBAC7D,GAAG,EAAE,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,qBAAqB;aAC7E,CAAC,CAAC;SACJ,EAAE,OAAO,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRentalMarketTool(server: McpServer): void;
@@ -0,0 +1,111 @@
1
+ import { z } from "zod";
2
+ import { sfQuery } from "../../services/snowflake.js";
3
+ import { structuredResult, kvSummary, markdownTable } from "../formatters.js";
4
+ import { action } from "../nextActions.js";
5
+ import { registerToolSafe } from "../registerToolSafe.js";
6
+ const RENT_TABLE = "SFRA_PROD.PUBLIC.RENT_VIEW";
7
+ const Input = z.object({
8
+ zipCode: z.string().optional().describe("5-digit ZIP code"),
9
+ city: z.string().optional().describe("City name (if no ZIP)"),
10
+ state: z.string().describe("2-letter state code"),
11
+ bedrooms: z.coerce.number().optional().describe("Filter by bedroom count"),
12
+ propertyType: z.string().optional().describe("Filter by home type, e.g. 'SINGLE_FAMILY'"),
13
+ lookbackMonths: z.coerce.number().optional().describe("Months of data (default 12)"),
14
+ });
15
+ function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
16
+ function buildWhere(args, alias = "r") {
17
+ const c = [];
18
+ const b = [];
19
+ if (args.zipCode) {
20
+ c.push(`${alias}.ADDRESS_ZIPCODE = ?`);
21
+ b.push(args.zipCode);
22
+ }
23
+ else if (args.city) {
24
+ c.push(`UPPER(${alias}.LOCATION_CITY) = UPPER(?)`);
25
+ b.push(args.city);
26
+ }
27
+ c.push(`${alias}.LOCATION_STATE_ABBR = ?`);
28
+ b.push(args.state.toUpperCase());
29
+ c.push(`${alias}.PRICE IS NOT NULL`, `${alias}.PRICE > 0`, `${alias}.PRICE < 25000`);
30
+ if (args.bedrooms !== undefined) {
31
+ c.push(`${alias}.BEDROOMS = ?`);
32
+ b.push(args.bedrooms);
33
+ }
34
+ if (args.propertyType) {
35
+ c.push(`UPPER(${alias}.HOMETYPE) = UPPER(?)`);
36
+ b.push(args.propertyType);
37
+ }
38
+ return { where: c.join(" AND "), binds: b };
39
+ }
40
+ export function registerRentalMarketTool(server) {
41
+ registerToolSafe(server, "sfr_rental_market_sf", {
42
+ title: "Rental pricing stats from Snowflake (percentiles, type breakdown)",
43
+ description: "Get rental market stats from 5.9M listings: median/avg rent, rent/sqft, " +
44
+ "percentile distribution (p10-p90), property type breakdown. " +
45
+ "Queries RENT_VIEW in Snowflake for richer data than the HTTP API.",
46
+ inputSchema: Input,
47
+ }, async (args) => {
48
+ const lookback = args.lookbackMonths ?? 12;
49
+ const { where, binds } = buildWhere(args);
50
+ const [statsRows, typeRows] = await Promise.all([
51
+ sfQuery(`SELECT COUNT(*) AS TOTAL, MEDIAN(r.PRICE) AS MED_RENT, AVG(r.PRICE) AS AVG_RENT,
52
+ AVG(CASE WHEN r.LIVINGAREA > 0 THEN r.PRICE / r.LIVINGAREA ELSE NULL END) AS AVG_RPSF,
53
+ PERCENTILE_CONT(0.10) WITHIN GROUP (ORDER BY r.PRICE) AS P10,
54
+ PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY r.PRICE) AS P25,
55
+ PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY r.PRICE) AS P50,
56
+ PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY r.PRICE) AS P75,
57
+ PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY r.PRICE) AS P90,
58
+ AVG(r.BEDROOMS) AS AVG_BEDS, AVG(r.LIVINGAREA) AS AVG_SQFT
59
+ FROM ${RENT_TABLE} r WHERE ${where} AND r.EFFECTIVE_DATE >= DATEADD(month, -?, CURRENT_DATE)`, [...binds, lookback]),
60
+ sfQuery(`SELECT r.HOMETYPE, COUNT(*) AS CNT, MEDIAN(r.PRICE) AS MED_RENT
61
+ FROM ${RENT_TABLE} r WHERE ${where} AND r.EFFECTIVE_DATE >= DATEADD(month, -?, CURRENT_DATE)
62
+ AND r.HOMETYPE IS NOT NULL GROUP BY r.HOMETYPE ORDER BY CNT DESC LIMIT 8`, [...binds, lookback]),
63
+ ]);
64
+ const s = statsRows[0];
65
+ if (!s || s.TOTAL === 0) {
66
+ return structuredResult(`## Rental Market\n\n_No data for ${args.zipCode ?? args.city}, ${args.state}_`, {});
67
+ }
68
+ const loc = args.zipCode ?? args.city ?? "Unknown";
69
+ const lines = [
70
+ `## Rental Market — ${loc}, ${args.state.toUpperCase()} (last ${lookback} months)`,
71
+ "",
72
+ "### Pricing",
73
+ kvSummary([
74
+ ["Total Listings", s.TOTAL.toLocaleString()],
75
+ ["Median Rent", fmt$(s.MED_RENT)],
76
+ ["Average Rent", fmt$(s.AVG_RENT)],
77
+ ["Avg Rent/SqFt", s.AVG_RPSF !== null ? `$${s.AVG_RPSF.toFixed(2)}` : undefined],
78
+ ]),
79
+ "",
80
+ "### Price Distribution",
81
+ kvSummary([
82
+ ["10th %ile", fmt$(s.P10)],
83
+ ["25th %ile", fmt$(s.P25)],
84
+ ["Median", fmt$(s.P50)],
85
+ ["75th %ile", fmt$(s.P75)],
86
+ ["90th %ile", fmt$(s.P90)],
87
+ ]),
88
+ "",
89
+ "### Market Characteristics",
90
+ kvSummary([
91
+ ["Avg Bedrooms", s.AVG_BEDS !== null ? s.AVG_BEDS.toFixed(1) : undefined],
92
+ ["Avg SqFt", s.AVG_SQFT !== null ? Math.round(s.AVG_SQFT).toLocaleString() : undefined],
93
+ ]),
94
+ ];
95
+ if (typeRows.length > 0) {
96
+ lines.push("", "### Property Type Breakdown");
97
+ lines.push(markdownTable(["Type", "Listings", "Median Rent"], typeRows.map((t) => [t.HOMETYPE, t.CNT.toLocaleString(), fmt$(t.MED_RENT)])));
98
+ }
99
+ const zip = args.zipCode ?? "";
100
+ const actions = [
101
+ action("sfr_rental_yield", "Yield analysis", { zipCode: zip, state: args.state }),
102
+ action("sfr_market_snapshot", "Sale market data", { zipCode: zip, state: args.state }),
103
+ ];
104
+ return structuredResult(lines.join("\n"), {
105
+ location: loc, state: args.state.toUpperCase(), totalListings: s.TOTAL,
106
+ medianRent: s.MED_RENT, avgRent: s.AVG_RENT, avgRentPerSqft: s.AVG_RPSF,
107
+ distribution: { p10: s.P10, p25: s.P25, p50: s.P50, p75: s.P75, p90: s.P90 },
108
+ }, actions);
109
+ });
110
+ }
111
+ //# sourceMappingURL=rentalMarket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rentalMarket.js","sourceRoot":"","sources":["../../../src/tools/snowflake/rentalMarket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerRentalYieldTool(server: McpServer): void;
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { sfQuery } from "../../services/snowflake.js";
3
+ import { structuredResult, kvSummary } from "../formatters.js";
4
+ import { action } from "../nextActions.js";
5
+ import { registerToolSafe } from "../registerToolSafe.js";
6
+ const RENT_TABLE = "SFRA_PROD.PUBLIC.RENT_VIEW";
7
+ const GEO_FLIPS_TABLE = "SFRA_PROD.PUBLIC.GEO_FLIPS";
8
+ const Input = z.object({
9
+ zipCode: z.string().optional().describe("5-digit ZIP code (required for acquisition yield)"),
10
+ city: z.string().optional().describe("City name (market yield only, no acquisition data)"),
11
+ state: z.string().describe("2-letter state code"),
12
+ bedrooms: z.coerce.number().optional().describe("Filter by bedroom count"),
13
+ propertyType: z.string().optional().describe("Filter by home type"),
14
+ });
15
+ function fmt$(v) { return v !== null ? "$" + Math.round(v).toLocaleString() : "—"; }
16
+ function fmtPct(v, d = 1) { return v !== null ? (v * 100).toFixed(d) + "%" : "—"; }
17
+ function buildWhere(args, alias = "r") {
18
+ const c = [];
19
+ const b = [];
20
+ if (args.zipCode) {
21
+ c.push(`${alias}.ADDRESS_ZIPCODE = ?`);
22
+ b.push(args.zipCode);
23
+ }
24
+ else if (args.city) {
25
+ c.push(`UPPER(${alias}.LOCATION_CITY) = UPPER(?)`);
26
+ b.push(args.city);
27
+ }
28
+ c.push(`${alias}.LOCATION_STATE_ABBR = ?`);
29
+ b.push(args.state.toUpperCase());
30
+ c.push(`${alias}.PRICE IS NOT NULL`, `${alias}.PRICE > 0`, `${alias}.PRICE < 25000`);
31
+ if (args.bedrooms !== undefined) {
32
+ c.push(`${alias}.BEDROOMS = ?`);
33
+ b.push(args.bedrooms);
34
+ }
35
+ if (args.propertyType) {
36
+ c.push(`UPPER(${alias}.HOMETYPE) = UPPER(?)`);
37
+ b.push(args.propertyType);
38
+ }
39
+ return { where: c.join(" AND "), binds: b };
40
+ }
41
+ export function registerRentalYieldTool(server) {
42
+ registerToolSafe(server, "sfr_rental_yield", {
43
+ title: "Rental yield analysis: market yield + acquisition yield (Snowflake)",
44
+ description: "Two-part yield analysis: (1) MARKET YIELD — rent vs current home values (Zestimate/AVM), " +
45
+ "(2) ACQUISITION YIELD — rent vs recent investor purchase prices from deed records. " +
46
+ "Includes rent-to-price ratio, 1% rule check, cap rate rating, and discount/premium signal.",
47
+ inputSchema: Input,
48
+ }, async (args) => {
49
+ const st = args.state.toUpperCase();
50
+ const { where, binds } = buildWhere(args);
51
+ const [rentRows, acqRows] = await Promise.all([
52
+ sfQuery(`SELECT COUNT(*) AS TOTAL, MEDIAN(r.PRICE) AS MED_RENT, AVG(r.PRICE) AS AVG_RENT,
53
+ MEDIAN(r.ZESTIMATE) AS MED_ZEST, MEDIAN(r.CURRENT_AVM_VALUE) AS MED_AVM,
54
+ MEDIAN(CASE WHEN r.LIVINGAREA > 0 THEN r.PRICE / r.LIVINGAREA ELSE NULL END) AS MED_RPSF
55
+ FROM ${RENT_TABLE} r WHERE ${where} AND r.EFFECTIVE_DATE >= DATEADD(month, -12, CURRENT_DATE)`, binds),
56
+ args.zipCode
57
+ ? sfQuery(`SELECT COUNT(*) AS TX_CNT,
58
+ MEDIAN(CASE WHEN SALE_AMT > 10000 THEN SALE_AMT ELSE NULL END) AS MED_PRICE
59
+ FROM ${GEO_FLIPS_TABLE}
60
+ WHERE ZIP_CODE = ? AND STATE = ? AND RECORDING_DATE >= DATEADD(year, -2, CURRENT_DATE)`, [args.zipCode, st])
61
+ : Promise.resolve([{ TX_CNT: 0, MED_PRICE: null }]),
62
+ ]);
63
+ const r = rentRows[0];
64
+ if (!r || r.TOTAL === 0) {
65
+ return structuredResult(`## Rental Yield\n\n_No data for ${args.zipCode ?? args.city}, ${st}_`, {});
66
+ }
67
+ const acq = acqRows[0] ?? { TX_CNT: 0, MED_PRICE: null };
68
+ const annualRent = r.MED_RENT !== null ? r.MED_RENT * 12 : null;
69
+ const mktVal = r.MED_AVM ?? r.MED_ZEST;
70
+ const mktYield = annualRent !== null && mktVal && mktVal > 0 ? annualRent / mktVal : null;
71
+ const acqYield = annualRent !== null && acq.MED_PRICE && acq.MED_PRICE > 0 ? annualRent / acq.MED_PRICE : null;
72
+ const rtp = r.MED_RENT !== null && mktVal && mktVal > 0 ? r.MED_RENT / mktVal : null;
73
+ let rating = "—";
74
+ if (mktYield !== null) {
75
+ if (mktYield >= 0.08)
76
+ rating = "STRONG CASH FLOW (>8%)";
77
+ else if (mktYield >= 0.06)
78
+ rating = "GOOD CASH FLOW (6-8%)";
79
+ else if (mktYield >= 0.04)
80
+ rating = "MODERATE (4-6%)";
81
+ else
82
+ rating = "APPRECIATION PLAY (<4%)";
83
+ }
84
+ let rtpNote = "";
85
+ if (rtp !== null) {
86
+ if (rtp >= 0.01)
87
+ rtpNote = " (meets 1% rule)";
88
+ else if (rtp >= 0.008)
89
+ rtpNote = " (close to 1% rule)";
90
+ else
91
+ rtpNote = " (below 1% rule)";
92
+ }
93
+ let discountNote = "";
94
+ if (acqYield !== null && mktYield !== null) {
95
+ if (acqYield > mktYield * 1.1)
96
+ discountNote = `Investors buying at discount — acq yield ${fmtPct(acqYield)} > market ${fmtPct(mktYield)}`;
97
+ else if (acqYield < mktYield * 0.9)
98
+ discountNote = `Investors paying premium — acq yield ${fmtPct(acqYield)} < market ${fmtPct(mktYield)}`;
99
+ }
100
+ const loc = args.zipCode ?? args.city ?? "Unknown";
101
+ const lines = [
102
+ `## Rental Yield — ${loc}, ${st}`,
103
+ "",
104
+ "### Rental Income",
105
+ kvSummary([
106
+ ["Median Rent", `${fmt$(r.MED_RENT)}/mo (${fmt$(annualRent)}/yr)`],
107
+ ["Rent/SqFt", r.MED_RPSF !== null ? `$${r.MED_RPSF.toFixed(2)}` : undefined],
108
+ ]),
109
+ "",
110
+ "### Market Yield (rent vs current values)",
111
+ kvSummary([
112
+ ["Median Zestimate", fmt$(r.MED_ZEST)],
113
+ ["Median AVM", fmt$(r.MED_AVM)],
114
+ ["Market Cap Rate", fmtPct(mktYield)],
115
+ ]),
116
+ ];
117
+ if (args.zipCode) {
118
+ lines.push("", "### Acquisition Yield (rent vs recent purchases, 2yr)", kvSummary([
119
+ ["Recent Investor Transactions", acq.TX_CNT.toLocaleString()],
120
+ ["Median Purchase Price", fmt$(acq.MED_PRICE)],
121
+ ["Acquisition Cap Rate", fmtPct(acqYield)],
122
+ ]));
123
+ }
124
+ lines.push("", "### Investment Context", kvSummary([
125
+ ["Rent-to-Price Ratio", rtp !== null ? `${(rtp * 100).toFixed(3)}%${rtpNote}` : undefined],
126
+ ["Rating", rating],
127
+ ...(discountNote ? [["Note", discountNote]] : []),
128
+ ]));
129
+ const zip = args.zipCode ?? "";
130
+ const actions = [
131
+ action("sfr_rental_market_sf", "Full rental stats", { zipCode: zip, state: st }),
132
+ action("sfr_zip_investor_activity", "Investor transactions", { zipCode: zip, state: st }),
133
+ action("sfr_market_snapshot", "Sale market data", { zipCode: zip, state: st }),
134
+ ];
135
+ return structuredResult(lines.join("\n"), {
136
+ location: loc, state: st, medianRent: r.MED_RENT,
137
+ medianZestimate: r.MED_ZEST, medianAvm: r.MED_AVM, marketYield: mktYield,
138
+ recentTxCount: acq.TX_CNT, medianPurchasePrice: acq.MED_PRICE, acquisitionYield: acqYield,
139
+ rentToPrice: rtp, rating,
140
+ }, actions);
141
+ });
142
+ }
143
+ //# sourceMappingURL=rentalYield.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rentalYield.js","sourceRoot":"","sources":["../../../src/tools/snowflake/rentalYield.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerZipProfileTool(server: McpServer): void;