@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,98 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, fmtDollars } from "../formatters.js";
3
+ import { action, locationArgs } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { resolveCanonicalMsa } from "./msaResolver.js";
6
+ const Input = z.object({
7
+ search_type: z
8
+ .enum(["state", "city", "zip", "msa"])
9
+ .describe("Location search type"),
10
+ state: z.string().optional().describe("State abbreviation, e.g. 'AZ'"),
11
+ city: z.string().optional().describe("City name (requires state param when search_type='city')"),
12
+ zips: z.string().optional().describe("Comma-separated zip codes, e.g. '85281'"),
13
+ msa: z.string().optional().describe("MSA name"),
14
+ metric: z
15
+ .enum(["hold-time", "gross-margin", "both"])
16
+ .default("both")
17
+ .describe("Which flip metric to retrieve"),
18
+ });
19
+ /** Format monthly time series as a readable table (last 12 months) */
20
+ function formatTimeSeries(items, valueKey, formatValue, valueHeader) {
21
+ if (!Array.isArray(items) || items.length === 0)
22
+ return "_No data_";
23
+ // Show last 12 months
24
+ const recent = items.slice(-12);
25
+ const rows = recent.map((row) => {
26
+ const date = row.date ?? "—";
27
+ const month = date.length >= 7 ? date.slice(0, 7) : date;
28
+ const val = row[valueKey];
29
+ return [month, val != null ? formatValue(Number(val)) : "—"];
30
+ });
31
+ return markdownTable(["Month", valueHeader], rows);
32
+ }
33
+ export function registerFlipStatsTool(server, sfr) {
34
+ registerToolSafe(server, "sfr_flip_stats", {
35
+ title: "Flip profitability stats (SFR)",
36
+ description: "Get flip profitability TRENDS for a market: hold time ranges and gross margin ranges. " +
37
+ "Choose metric='hold-time', 'gross-margin', or 'both'. " +
38
+ "Requires search_type + location param (search_type='city' requires state). " +
39
+ "For market-level flip aggregates (volume, flipper counts), use sfr_flip_activity.",
40
+ inputSchema: Input,
41
+ }, async (args) => {
42
+ const query = { search_type: args.search_type };
43
+ if (args.state)
44
+ query.state = args.state;
45
+ if (args.city)
46
+ query.city = args.city;
47
+ if (args.zips)
48
+ query.zips = args.zips;
49
+ if (args.msa)
50
+ query.msa = args.msa;
51
+ let effectiveMsa = args.msa;
52
+ let resolutionMethod = "exact";
53
+ // MSA resolution probe — check before making metric-specific calls
54
+ if (args.search_type === "msa" && args.msa) {
55
+ const probeData = await sfr.getFlipHoldTime(query);
56
+ const probeItems = Array.isArray(probeData) ? probeData : probeData?.data ?? [];
57
+ if (probeItems.length === 0) {
58
+ resolutionMethod = "none";
59
+ const resolved = await resolveCanonicalMsa(sfr, { requestedMsa: args.msa });
60
+ if (resolved.resolvedMsa
61
+ && resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
62
+ effectiveMsa = resolved.resolvedMsa;
63
+ query.msa = effectiveMsa;
64
+ resolutionMethod = "fallback";
65
+ }
66
+ }
67
+ }
68
+ const loc = args.zips ?? args.city ?? (effectiveMsa ?? args.msa) ?? args.state ?? "All";
69
+ const lines = [`## Flip Statistics — ${loc}`, ""];
70
+ if (resolutionMethod === "fallback") {
71
+ lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
72
+ }
73
+ const result = {};
74
+ if (args.metric !== "gross-margin") {
75
+ const holdTime = await sfr.getFlipHoldTime(query);
76
+ const items = Array.isArray(holdTime) ? holdTime : holdTime?.data ?? [];
77
+ result.holdTime = items;
78
+ lines.push("### Median Hold Time (last 12 months)");
79
+ lines.push(formatTimeSeries(items, "daysDiff", (v) => `${Math.round(v)} days`, "Median Days"));
80
+ lines.push("");
81
+ }
82
+ if (args.metric !== "hold-time") {
83
+ const margin = await sfr.getFlipGrossMargin(query);
84
+ const items = Array.isArray(margin) ? margin : margin?.data ?? [];
85
+ result.grossMargin = items;
86
+ lines.push("### Median Gross Margin (last 12 months)");
87
+ lines.push(formatTimeSeries(items, "grossMargin", (v) => fmtDollars(v), "Median Margin"));
88
+ }
89
+ const locArgs = locationArgs(args);
90
+ const actions = [
91
+ action("sfr_flip_activity", "Browse individual flip transactions", locArgs),
92
+ action("sfr_search_properties", "All transactions in area", locArgs),
93
+ action("sfr_top_buyers", "Active buyers in area", locArgs),
94
+ ];
95
+ return structuredResult(lines.join("\n"), result, actions);
96
+ });
97
+ }
98
+ //# sourceMappingURL=flipStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flipStats.js","sourceRoot":"","sources":["../../../src/tools/sfr/flipStats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SfrClient } from "../../services/sfr.js";
3
+ export declare function registerGetPropertyTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,142 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, kvSummary } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ const Input = z.object({
6
+ address: z.string().describe("Full property address, e.g. '123 Main St, Phoenix, AZ 85004'"),
7
+ });
8
+ export function registerGetPropertyTool(server, sfr) {
9
+ registerToolSafe(server, "sfr_get_property", {
10
+ title: "Get property details by address (SFR)",
11
+ description: "Get comprehensive property details by address: valuation (AVM), tax assessments, " +
12
+ "owner info, structure (beds/baths/sqft), last sale, lot size, and investment metrics. " +
13
+ "For multiple addresses at once, use sfr_property_batch. " +
14
+ "For transaction history, use sfr_property_transactions.",
15
+ inputSchema: Input,
16
+ }, async (args) => {
17
+ const data = await sfr.getPropertyByAddress(args.address);
18
+ const addr = data?.address;
19
+ const fullAddr = addr
20
+ ? `${addr.formatted_street_address}, ${addr.city}, ${addr.state} ${addr.zip_code}`
21
+ : args.address;
22
+ const lines = [
23
+ `## Property Details — ${fullAddr}`,
24
+ "",
25
+ "### Location",
26
+ kvSummary([
27
+ ["Address", addr?.formatted_street_address],
28
+ ["City", addr?.city],
29
+ ["State", addr?.state],
30
+ ["Zip", addr?.zip_code],
31
+ ["County", data?.county],
32
+ ["MSA", data?.msa],
33
+ ["Latitude", addr?.latitude],
34
+ ["Longitude", addr?.longitude],
35
+ ]),
36
+ "",
37
+ "### Structure",
38
+ kvSummary([
39
+ ["Beds", data?.structure?.beds_count],
40
+ ["Baths", data?.structure?.baths],
41
+ ["Sqft", data?.structure?.total_area_sq_ft ?? data?.structure?.living_area_sqft],
42
+ ["Lot Size (sqft)", data?.parcel?.area_sq_ft],
43
+ ["Year Built", data?.structure?.year_built],
44
+ ["Stories", data?.structure?.stories],
45
+ ["Property Type", data?.property_type ?? data?.property_class_description],
46
+ ["Units", data?.structure?.units_count],
47
+ ]),
48
+ "",
49
+ "### Valuation & Assessments",
50
+ kvSummary([
51
+ ["Assessed Value", data?.assessments?.assessed_value ? `$${Number(data.assessments.assessed_value).toLocaleString()}` : undefined],
52
+ ["Land Value", data?.assessments?.land_value ? `$${Number(data.assessments.land_value).toLocaleString()}` : undefined],
53
+ ["Market Value", data?.assessments?.market_value ? `$${Number(data.assessments.market_value).toLocaleString()}` : undefined],
54
+ ["AVM", data?.valuation?.value ? `$${Number(data.valuation.value).toLocaleString()}` : undefined],
55
+ ["Tax Amount", data?.tax_amount ? `$${Number(data.tax_amount).toLocaleString()}` : undefined],
56
+ ]),
57
+ "",
58
+ "### Last Sale",
59
+ kvSummary([
60
+ ["Date", data?.last_sale?.date],
61
+ ["Price", data?.last_sale?.price ? `$${Number(data.last_sale.price).toLocaleString()}` : undefined],
62
+ ["Document Type", data?.last_sale?.document_type],
63
+ ["Mortgage Amount", data?.last_sale?.mtg_amount ? `$${Number(data.last_sale.mtg_amount).toLocaleString()}` : undefined],
64
+ ["Lender", data?.last_sale?.lender],
65
+ ["Months Owned", data?.months_owned],
66
+ ]),
67
+ "",
68
+ "### Owner",
69
+ kvSummary([
70
+ ["Name", data?.owner?.name],
71
+ ["Owner Occupied", data?.owner?.owner_occupied],
72
+ ["Corporate", data?.owner?.corporate_owner],
73
+ ["Owner Type", data?.owner_type],
74
+ ["Purchase Method", data?.purchase_method],
75
+ ]),
76
+ "",
77
+ "### Status",
78
+ kvSummary([
79
+ ["Listing Status", data?.listing_status],
80
+ ["HOA", data?.hoa],
81
+ ["Vacant", data?.vacant],
82
+ ["Pre-foreclosure", data?.pre_foreclosure?.flag],
83
+ ]),
84
+ ];
85
+ const actions = [
86
+ action("sfr_rental_comparables", "Estimate rental income", { address: fullAddr }),
87
+ action("sfr_property_comps", "Sales comps for valuation", { address: fullAddr }),
88
+ action("sfr_property_transactions", "Full ownership history", { address: fullAddr }),
89
+ action("plr_loans_nearby", "Private lending activity nearby", { propertyAddress: fullAddr }),
90
+ ];
91
+ const structured = {
92
+ address: addr ? {
93
+ formatted_street_address: addr.formatted_street_address,
94
+ city: addr.city,
95
+ state: addr.state,
96
+ zip_code: addr.zip_code,
97
+ latitude: addr.latitude,
98
+ longitude: addr.longitude,
99
+ } : undefined,
100
+ county: data?.county,
101
+ msa: data?.msa,
102
+ structure: data?.structure ? {
103
+ beds_count: data.structure.beds_count,
104
+ baths: data.structure.baths,
105
+ total_area_sq_ft: data.structure.total_area_sq_ft ?? data.structure.living_area_sqft,
106
+ year_built: data.structure.year_built,
107
+ stories: data.structure.stories,
108
+ units_count: data.structure.units_count,
109
+ } : undefined,
110
+ property_type: data?.property_type ?? data?.property_class_description,
111
+ lot_sq_ft: data?.parcel?.area_sq_ft,
112
+ assessments: data?.assessments ? {
113
+ assessed_value: data.assessments.assessed_value,
114
+ land_value: data.assessments.land_value,
115
+ market_value: data.assessments.market_value,
116
+ } : undefined,
117
+ valuation: data?.valuation?.value != null ? { value: data.valuation.value } : undefined,
118
+ tax_amount: data?.tax_amount,
119
+ last_sale: data?.last_sale ? {
120
+ date: data.last_sale.date,
121
+ price: data.last_sale.price,
122
+ document_type: data.last_sale.document_type,
123
+ mtg_amount: data.last_sale.mtg_amount,
124
+ lender: data.last_sale.lender,
125
+ } : undefined,
126
+ months_owned: data?.months_owned,
127
+ owner: data?.owner ? {
128
+ name: data.owner.name,
129
+ owner_occupied: data.owner.owner_occupied,
130
+ corporate_owner: data.owner.corporate_owner,
131
+ } : undefined,
132
+ owner_type: data?.owner_type,
133
+ purchase_method: data?.purchase_method,
134
+ listing_status: data?.listing_status,
135
+ hoa: data?.hoa,
136
+ vacant: data?.vacant,
137
+ pre_foreclosure: data?.pre_foreclosure?.flag,
138
+ };
139
+ return structuredResult(lines.join("\n"), structured, actions);
140
+ });
141
+ }
142
+ //# sourceMappingURL=getProperty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getProperty.js","sourceRoot":"","sources":["../../../src/tools/sfr/getProperty.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SfrClient } from "../../services/sfr.js";
3
+ export declare function registerInstitutionalOwnersTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,88 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
3
+ import { action, locationArgs } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { resolveCanonicalMsa } from "./msaResolver.js";
6
+ const Input = z.object({
7
+ search_type: z.enum(["state", "city", "zip", "msa"]).describe("Location search type"),
8
+ state: z.string().optional().describe("State abbreviation, e.g. 'AZ'"),
9
+ city: z.string().optional().describe("City name (requires state param when search_type='city')"),
10
+ zips: z.string().optional().describe("Comma-separated zip codes"),
11
+ msa: z.string().optional().describe("MSA name"),
12
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
13
+ page_size: z.coerce.number().default(25).describe("Results per page (default 25, max 100)"),
14
+ });
15
+ export function registerInstitutionalOwnersTool(server, sfr) {
16
+ registerToolSafe(server, "sfr_institutional_owners", {
17
+ title: "Institutional REITs & funds owning SFR in area (SFR)",
18
+ description: "Find institutional SFR owners (REITs and private equity funds) in a location, " +
19
+ "ranked by portfolio size. Covers tracked entities like Invitation Homes, American Homes 4 Rent, " +
20
+ "Progress Residential, Tricon, FirstKey, etc. " +
21
+ "Different from sfr_top_buyers which shows ALL active buyers (individuals + institutions). " +
22
+ "Different from sfr_market_highlights which shows MSA-only buyer rankings by acquisition count. " +
23
+ "NOTE: search_type='city' requires state param (e.g. city='Phoenix', state='AZ'). " +
24
+ "Use sfr_buyer_profile to get full profile for any listed owner. " +
25
+ "CHAIN: To find where an institution is SELLING, use sfr_search_properties with seller_name filter (e.g. seller_name='INVITATION').",
26
+ inputSchema: Input,
27
+ }, async (args) => {
28
+ const query = { search_type: args.search_type };
29
+ if (args.state)
30
+ query.state = args.state;
31
+ if (args.city)
32
+ query.city = args.city;
33
+ if (args.zips)
34
+ query.zips = args.zips;
35
+ if (args.msa)
36
+ query.msa = args.msa;
37
+ query.page = args.page;
38
+ query.page_size = args.page_size;
39
+ let effectiveMsa = args.msa;
40
+ let resolutionMethod = "exact";
41
+ const data = await sfr.getInstitutionalLargestOwners(query);
42
+ let items = data?.data ?? (Array.isArray(data) ? data : []);
43
+ let total = data?.total ?? items.length;
44
+ if (items.length === 0 && args.search_type === "msa" && args.msa) {
45
+ resolutionMethod = "none";
46
+ const resolved = await resolveCanonicalMsa(sfr, { requestedMsa: args.msa });
47
+ if (resolved.resolvedMsa
48
+ && resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
49
+ effectiveMsa = resolved.resolvedMsa;
50
+ const retryData = await sfr.getInstitutionalLargestOwners({ ...query, msa: effectiveMsa });
51
+ items = retryData?.data ?? (Array.isArray(retryData) ? retryData : []);
52
+ total = retryData?.total ?? items.length;
53
+ if (items.length > 0)
54
+ resolutionMethod = "fallback";
55
+ }
56
+ }
57
+ const loc = args.zips ?? args.city ?? (effectiveMsa ?? args.msa) ?? args.state ?? "All";
58
+ const lines = [
59
+ `## Institutional Owners — ${loc}`,
60
+ `**Total:** ${total}`,
61
+ "",
62
+ ];
63
+ if (resolutionMethod === "fallback") {
64
+ lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
65
+ }
66
+ if (items.length > 0) {
67
+ const rows = items.slice(0, 25).map((o, i) => [
68
+ i + 1,
69
+ o.fundName ?? o.name ?? o.owner_name ?? "—",
70
+ o.totalHomes ?? o.properties ?? o.property_count ?? "—",
71
+ ]);
72
+ lines.push(markdownTable(["#", "Owner", "Properties"], rows));
73
+ }
74
+ else {
75
+ lines.push("```json");
76
+ lines.push(truncatedJson(data, 3000));
77
+ lines.push("```");
78
+ }
79
+ const firstName = items[0]?.fundName ?? items[0]?.name ?? items[0]?.owner_name;
80
+ const actions = [
81
+ ...(firstName ? [action("sfr_buyer_profile", "Profile top institutional owner", { name: firstName })] : []),
82
+ ...(args.zips ? [action("sfr_zip_detail", "Deep-dive zip metrics", { zipCode: args.zips.split(",")[0] })] : []),
83
+ action("sfr_top_buyers", "All buyers (not just institutional)", locationArgs(args)),
84
+ ];
85
+ return structuredResult(lines.join("\n"), { items, total, page: args.page }, actions);
86
+ });
87
+ }
88
+ //# sourceMappingURL=institutionalOwners.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"institutionalOwners.js","sourceRoot":"","sources":["../../../src/tools/sfr/institutionalOwners.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SfrClient } from "../../services/sfr.js";
3
+ export declare function registerInvestorActivityTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { autoFillDateRange } from "../dateHelper.js";
6
+ import { resolveCanonicalMsa } from "./msaResolver.js";
7
+ const Input = z.object({
8
+ msa: z.string().describe("MSA name, e.g. 'Phoenix-Mesa-Chandler, AZ'"),
9
+ sales_date_min: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
10
+ sales_date_max: z.string().optional().describe("End date"),
11
+ is_cash_buyer: z.boolean().optional().describe("Filter for cash purchases only"),
12
+ seller_name: z.string().optional().describe("Filter by seller name (partial match)"),
13
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
14
+ page_size: z.coerce.number().default(20).describe("Results per page (default 20, max 100)"),
15
+ });
16
+ export function registerInvestorActivityTool(server, sfr) {
17
+ registerToolSafe(server, "sfr_investor_activity", {
18
+ title: "Browse investor transactions in MSA — with portfolio size (SFR)",
19
+ description: "Browse individual investor PURCHASE transactions in an MSA market. " +
20
+ "Best for BUYER analysis: shows cash vs financed, corporate vs individual, and buyer's total portfolio size. " +
21
+ "Each transaction includes isCashBuyer flag — use is_cash_buyer=true to find cash buyer prospects. " +
22
+ "Also supports seller_name filter, but for 'where is [institution] selling?' queries prefer sfr_search_properties which has broader date coverage. " +
23
+ "To calculate yields on purchases, chain with sfr_rental_stats by zip: yield = (annual rent / purchase price). " +
24
+ "Requires an MSA name. For non-MSA transaction searches, use sfr_search_properties. " +
25
+ "See sfr_buyer_profile for full profiles and sfr_flip_activity for flip stats.",
26
+ inputSchema: Input,
27
+ }, async (args) => {
28
+ const query = {
29
+ search_type: "msa",
30
+ msa: args.msa,
31
+ page: args.page,
32
+ page_size: args.page_size,
33
+ };
34
+ if (args.sales_date_min)
35
+ query.sales_date_min = args.sales_date_min;
36
+ if (args.sales_date_max)
37
+ query.sales_date_max = args.sales_date_max;
38
+ if (args.is_cash_buyer !== undefined)
39
+ query.is_cash_buyer = args.is_cash_buyer;
40
+ if (args.seller_name)
41
+ query.seller_name = args.seller_name;
42
+ autoFillDateRange(query);
43
+ let effectiveMsa = args.msa;
44
+ let resolutionMethod = "exact";
45
+ const data = await sfr.getBuyerActivity(query);
46
+ let items = Array.isArray(data) ? data : data?.data ?? [];
47
+ if (items.length === 0) {
48
+ resolutionMethod = "none";
49
+ const resolved = await resolveCanonicalMsa(sfr, {
50
+ requestedMsa: args.msa,
51
+ salesDateMin: typeof query.sales_date_min === "string" ? query.sales_date_min : undefined,
52
+ salesDateMax: typeof query.sales_date_max === "string" ? query.sales_date_max : undefined,
53
+ });
54
+ if (resolved.resolvedMsa
55
+ && resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
56
+ effectiveMsa = resolved.resolvedMsa;
57
+ const retryQuery = { ...query, msa: effectiveMsa };
58
+ const retryData = await sfr.getBuyerActivity(retryQuery);
59
+ items = Array.isArray(retryData) ? retryData : retryData?.data ?? [];
60
+ if (items.length > 0)
61
+ resolutionMethod = "fallback";
62
+ }
63
+ }
64
+ const loc = effectiveMsa;
65
+ const lines = [
66
+ `## Market Investor Activity — ${loc}`,
67
+ `**Showing:** ${items.length} transactions`,
68
+ "",
69
+ ];
70
+ if (resolutionMethod === "fallback") {
71
+ lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
72
+ }
73
+ if (items.length > 0) {
74
+ const fmt$ = (v) => v != null ? `$${Number(v).toLocaleString()}` : "—";
75
+ const rows = items.slice(0, 25).map((t) => [
76
+ t.buyerName ?? "—",
77
+ t.address ? `${t.address}, ${t.city ?? ""}` : "—",
78
+ fmt$(t.saleValue),
79
+ t.isCashBuyer ? "Cash" : "Financed",
80
+ t.isCorporate ? "Corp" : "Individual",
81
+ t.buyerPropertiesCount ?? "—",
82
+ ]);
83
+ lines.push(markdownTable(["Buyer", "Address", "Price", "Type", "Entity", "Portfolio"], rows));
84
+ }
85
+ else {
86
+ lines.push("_No transactions found for this market._ " +
87
+ "Try widening the date range" +
88
+ (args.seller_name ? ` or using a shorter seller name (partial match on '${args.seller_name}')` : "") +
89
+ (args.is_cash_buyer ? " or removing the cash buyer filter" : "") +
90
+ ".");
91
+ }
92
+ // Slim items — raw API has 30+ fields, many null (exit, profit, URL slugs, lat/lng)
93
+ const slimItems = items.slice(0, 25).map((t) => ({
94
+ buyerName: t.buyerName,
95
+ address: t.address,
96
+ city: t.city,
97
+ zip: t.zipCode,
98
+ state: t.state,
99
+ propertyType: t.propertyType,
100
+ saleValue: t.saleValue,
101
+ saleDate: t.saleDate,
102
+ avmValue: t.avmValue || undefined,
103
+ isCashBuyer: t.isCashBuyer || undefined,
104
+ isCorporate: t.isCorporate || undefined,
105
+ isDiscountedPurchase: t.isDiscountedPurchase || undefined,
106
+ loanAmount: t.loanAmount || undefined,
107
+ lenderName: t.lenderName || undefined,
108
+ buyerPropertiesCount: t.buyerPropertiesCount || undefined,
109
+ buyerTransactionsCount: t.buyerTransactionsCount || undefined,
110
+ sqft: t.buildingArea || undefined,
111
+ beds: t.bedrooms || undefined,
112
+ }));
113
+ const firstBuyer = items[0]?.buyerName;
114
+ const firstAddr = items[0] ? `${items[0].address}, ${items[0].city}, ${items[0].state} ${items[0].zipCode}` : undefined;
115
+ const actions = [
116
+ ...(firstBuyer ? [action("sfr_buyer_profile", "Profile top investor", { name: firstBuyer })] : []),
117
+ ...(firstAddr ? [action("sfr_get_property", "Details on first property", { address: firstAddr })] : []),
118
+ action("sfr_search_properties", "Search with more filters", { search_type: "msa", msa: effectiveMsa }),
119
+ ];
120
+ return structuredResult(lines.join("\n"), {
121
+ items: slimItems,
122
+ total: items.length,
123
+ page: args.page,
124
+ requested_msa: args.msa,
125
+ resolved_msa: effectiveMsa,
126
+ msa_resolution_method: resolutionMethod,
127
+ }, actions);
128
+ });
129
+ }
130
+ //# sourceMappingURL=investorActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"investorActivity.js","sourceRoot":"","sources":["../../../src/tools/sfr/investorActivity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SfrClient } from "../../services/sfr.js";
3
+ export declare function registerMarketHighlightsTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { autoFillDateRange } from "../dateHelper.js";
6
+ import { resolveCanonicalMsa } from "./msaResolver.js";
7
+ const Input = z.object({
8
+ msa: z.string().describe("MSA name, e.g. 'Phoenix-Mesa-Chandler, AZ'"),
9
+ city: z.string().optional().describe("Optional city filter within the MSA"),
10
+ sales_date_min: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
11
+ sales_date_max: z.string().optional().describe("End date"),
12
+ page_size: z.coerce.number().default(25).describe("Results per page (default 25, max 100)"),
13
+ });
14
+ export function registerMarketHighlightsTool(server, sfr) {
15
+ registerToolSafe(server, "sfr_market_highlights", {
16
+ title: "Top buyers in MSA market (SFR)",
17
+ description: "Rank buyers in an MSA market by acquisition count. Requires an MSA name. " +
18
+ "Supports optional city filter and date range within the MSA. " +
19
+ "Different from sfr_top_buyers which accepts any geography (state/city/zip/MSA). " +
20
+ "Different from sfr_institutional_owners which shows only institutional REITs/funds. " +
21
+ "Use sfr_buyer_profile to drill into any buyer.",
22
+ inputSchema: Input,
23
+ }, async (args) => {
24
+ const query = { msa: args.msa };
25
+ if (args.city)
26
+ query.city = args.city;
27
+ if (args.sales_date_min)
28
+ query.sales_date_min = args.sales_date_min;
29
+ if (args.sales_date_max)
30
+ query.sales_date_max = args.sales_date_max;
31
+ if (args.page_size)
32
+ query.page_size = args.page_size;
33
+ autoFillDateRange(query);
34
+ let effectiveMsa = args.msa;
35
+ let resolutionMethod = "exact";
36
+ const data = await sfr.getMarketTopBuyers(query);
37
+ // API ignores page_size param — enforce it client-side
38
+ let rawBuyers = Array.isArray(data) ? data : data?.data ?? [];
39
+ if (rawBuyers.length === 0) {
40
+ resolutionMethod = "none";
41
+ const resolved = await resolveCanonicalMsa(sfr, {
42
+ requestedMsa: args.msa,
43
+ salesDateMin: typeof query.sales_date_min === "string" ? query.sales_date_min : undefined,
44
+ salesDateMax: typeof query.sales_date_max === "string" ? query.sales_date_max : undefined,
45
+ });
46
+ if (resolved.resolvedMsa
47
+ && resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
48
+ effectiveMsa = resolved.resolvedMsa;
49
+ const retryQuery = { ...query, msa: effectiveMsa };
50
+ const retryData = await sfr.getMarketTopBuyers(retryQuery);
51
+ rawBuyers = Array.isArray(retryData) ? retryData : retryData?.data ?? [];
52
+ if (rawBuyers.length > 0)
53
+ resolutionMethod = "fallback";
54
+ }
55
+ }
56
+ const buyers = rawBuyers.slice(0, args.page_size);
57
+ const lines = [
58
+ `## Market Top Buyers — ${effectiveMsa}${args.city ? ` (${args.city})` : ""}`,
59
+ `**Showing:** ${buyers.length} buyers`,
60
+ "",
61
+ ];
62
+ if (resolutionMethod === "fallback") {
63
+ lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
64
+ }
65
+ if (buyers.length > 0) {
66
+ const rows = buyers.slice(0, 30).map((b, i) => [
67
+ i + 1,
68
+ b.name ?? "—",
69
+ b.acquisitions ?? b.count ?? "—",
70
+ b.hasContactInfo ? "Yes" : "No",
71
+ ]);
72
+ lines.push(markdownTable(["#", "Buyer", "Acquisitions", "Contact Info"], rows));
73
+ }
74
+ else {
75
+ lines.push("_No buyers found for this market._");
76
+ }
77
+ // Slim buyers — strip formattedName URL slugs and unnecessary metadata
78
+ const slimBuyers = buyers.slice(0, 50).map((b) => ({
79
+ name: b.name ?? b.buyer_name,
80
+ acquisitions: b.acquisitions ?? b.count,
81
+ hasProfile: b.hasProfilePage || undefined,
82
+ hasContact: b.hasContactInfo || undefined,
83
+ }));
84
+ const firstName = buyers[0]?.name ?? buyers[0]?.buyer_name;
85
+ const actions = [
86
+ ...(firstName ? [action("sfr_buyer_profile", "Profile top buyer", { name: firstName })] : []),
87
+ action("sfr_top_buyers", "Full buyer list with profiles", { search_type: "msa", msa: effectiveMsa }),
88
+ action("sfr_search_properties", "Browse individual transactions", { search_type: "msa", msa: effectiveMsa }),
89
+ ];
90
+ return structuredResult(lines.join("\n"), {
91
+ items: slimBuyers,
92
+ total: buyers.length,
93
+ page: 1,
94
+ requested_msa: args.msa,
95
+ resolved_msa: effectiveMsa,
96
+ msa_resolution_method: resolutionMethod,
97
+ }, actions);
98
+ });
99
+ }
100
+ //# sourceMappingURL=marketHighlights.js.map