@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,68 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { ApiError } from "../../services/httpClient.js";
6
+ import { slugifyBuyerName } from "../slugHelper.js";
7
+ const Input = z.object({
8
+ name: z.string().describe("Buyer/investor name or slug ('INVITATION HOMES' or 'invitation-homes')"),
9
+ });
10
+ export function registerBuyerGrowthTool(server, sfr) {
11
+ registerToolSafe(server, "sfr_buyer_growth", {
12
+ title: "Investor portfolio growth over time (SFR)",
13
+ description: "Track an investor's acquisition activity over time. Shows annual acquisition counts " +
14
+ "and cumulative portfolio size. " +
15
+ "Only works for major institutional investors — use sfr_top_buyers to discover names. " +
16
+ "Use after sfr_buyer_profile to understand HOW an investor is growing.",
17
+ inputSchema: Input,
18
+ }, async (args) => {
19
+ try {
20
+ const slug = slugifyBuyerName(args.name);
21
+ const data = await sfr.getBuyerGrowth(slug);
22
+ const items = data?.data ?? data?.growth ?? (Array.isArray(data) ? data : []);
23
+ const lines = [
24
+ `## Portfolio Growth — ${args.name}`,
25
+ `**Data Points:** ${items.length}`,
26
+ "",
27
+ ];
28
+ if (items.length > 0) {
29
+ // API returns {year, propertiesCount} (cumulative portfolio size by year)
30
+ // Compute YoY net acquisitions for additional insight
31
+ const rows = items.slice(0, 36).map((g, i) => {
32
+ const count = g.propertiesCount ?? g.acquisitions ?? g.count;
33
+ const prev = i > 0 ? (items[i - 1].propertiesCount ?? items[i - 1].count) : null;
34
+ const net = (count != null && prev != null) ? count - prev : null;
35
+ return [
36
+ g.year ?? g.period ?? g.date ?? g.month ?? "—",
37
+ count ?? "—",
38
+ net != null ? `+${net.toLocaleString()}` : "—",
39
+ ];
40
+ });
41
+ lines.push(markdownTable(["Year", "Properties", "Net Added"], rows));
42
+ }
43
+ else {
44
+ lines.push("```json");
45
+ lines.push(truncatedJson(data, 4000));
46
+ lines.push("```");
47
+ }
48
+ const actions = [
49
+ action("sfr_buyer_profile", "Full investor profile & strategy", { name: args.name }),
50
+ action("plr_borrower_search", "Check private lending activity", { search: args.name }),
51
+ ];
52
+ return structuredResult(lines.join("\n"), { growth: items }, actions);
53
+ }
54
+ catch (err) {
55
+ if (err instanceof ApiError && err.status === 404) {
56
+ const slug = slugifyBuyerName(args.name);
57
+ return structuredResult(`## Portfolio Growth — ${args.name}\n\n` +
58
+ `_No profile found (slug: "${slug}"). Only major institutional investors have profiles. ` +
59
+ `Use sfr_top_buyers to discover names with profiles. For local/individual investor growth trends, use plr_borrower_rankings instead._`, { name: args.name, slug, error: "not_found" }, [
60
+ action("sfr_top_buyers", "Find buyers with profiles", {}),
61
+ action("plr_borrower_rankings", "Growth trends for local investors", {}),
62
+ ]);
63
+ }
64
+ throw err;
65
+ }
66
+ });
67
+ }
68
+ //# sourceMappingURL=buyerGrowth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buyerGrowth.js","sourceRoot":"","sources":["../../../src/tools/sfr/buyerGrowth.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 registerBuyerProfileTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,162 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, kvSummary, markdownTable, fmtDollars } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { ApiError } from "../../services/httpClient.js";
6
+ import { slugifyBuyerName } from "../slugHelper.js";
7
+ const Input = z.object({
8
+ name: z.string().describe("Buyer/investor name or slug. Accepts display name ('INVITATION HOMES') " +
9
+ "or slug ('invitation-homes'). Use sfr_top_buyers to discover names."),
10
+ });
11
+ export function registerBuyerProfileTool(server, sfr) {
12
+ registerToolSafe(server, "sfr_buyer_profile", {
13
+ title: "Investor profile & strategy (SFR)",
14
+ description: "Get a comprehensive profile of a property investor/buyer by name. " +
15
+ "Returns portfolio stats (count, value, avg price), investment strategy (flip vs hold, " +
16
+ "target property type, price range), geographic focus, related LLCs, and contact info. " +
17
+ "Includes flip vs hold percentages and target margin ranges. " +
18
+ "Returns relatedLLCs — use these names with sfr_flip_activity(buyer_name=) to find flip transactions across all entities. " +
19
+ "For iBuyer/flipper margin analysis, chain: buyer_profile → get relatedLLCs → sfr_flip_activity per LLC. " +
20
+ "Only major institutional investors have profiles — use sfr_top_buyers to discover names. " +
21
+ "For acquisition trends over time, use sfr_buyer_growth. " +
22
+ "CHAIN: Use plr_borrower_search with the investor name to check if they borrow from private lenders.",
23
+ inputSchema: Input,
24
+ }, async (args) => {
25
+ try {
26
+ const slug = slugifyBuyerName(args.name);
27
+ const data = await sfr.getBuyerProfile(slug);
28
+ const lines = [`## Investor Profile — ${data?.name ?? args.name}`, ""];
29
+ if (data && typeof data === "object" && !data.statusCode) {
30
+ const p = data.portfolio;
31
+ lines.push(kvSummary([
32
+ ["Name", data.name],
33
+ ["Most Active MSA", data.mostActiveMsa],
34
+ ["Properties", p?.propertiesCount],
35
+ ["Portfolio Value", p?.portfolioValue ? fmtDollars(p.portfolioValue) : undefined],
36
+ ["Avg Purchase Price", p?.averagePurchasePrice ? fmtDollars(p.averagePurchasePrice) : undefined],
37
+ ["Avg Property Value", p?.averagePropertyValue ? fmtDollars(p.averagePropertyValue) : undefined],
38
+ ["Avg Purchase Year", p?.averagePurchaseYear != null ? Math.round(p.averagePurchaseYear) : undefined],
39
+ ["Avg Year Built", p?.averageYearBuilt != null ? Math.round(p.averageYearBuilt) : undefined],
40
+ ["Top Zip Codes", Array.isArray(p?.topZipCodes) ? p.topZipCodes.join(", ") : undefined],
41
+ ["Has Contact Info", data.hasContactInfo ? "Yes" : "No"],
42
+ ]));
43
+ // Related LLCs
44
+ if (Array.isArray(data.relatedLLCs) && data.relatedLLCs.length > 0) {
45
+ lines.push("", "### Related LLCs");
46
+ const rows = data.relatedLLCs.map((llc) => [
47
+ llc.formattedName ?? llc.name ?? "—",
48
+ llc.address ?? "—",
49
+ ]);
50
+ lines.push(markdownTable(["Name", "Address"], rows));
51
+ }
52
+ // Investor profile (AI-generated strategy analysis)
53
+ const ip = data.investorProfile;
54
+ if (ip) {
55
+ lines.push("", "### Investment Strategy");
56
+ if (ip.summary)
57
+ lines.push(ip.summary, "");
58
+ const bms = ip.businessModelStrategy;
59
+ if (bms) {
60
+ lines.push(kvSummary([
61
+ ["Primary Strategy", bms.primaryStrategy],
62
+ ["Flip %", bms.flipPercentage],
63
+ ["Hold %", bms.holdPercentage],
64
+ ["Positioning", bms.competitivePositioning],
65
+ ["Target Margin", bms.targetReturns?.marginRange],
66
+ ["Median Margin", bms.targetReturns?.medianMargin],
67
+ ]));
68
+ }
69
+ const dp = ip.dealParameters;
70
+ if (dp) {
71
+ lines.push("", "### Deal Parameters");
72
+ lines.push(kvSummary([
73
+ ["Price Range", dp.priceRange?.sweetSpot ? `${dp.priceRange.sweetSpot} (avg ${dp.priceRange.average})` : undefined],
74
+ ["Max Price", dp.priceRange?.maximum],
75
+ ["Target Size", dp.targetProperties?.size],
76
+ ["Target Config", dp.targetProperties?.configuration],
77
+ ["Condition", dp.targetProperties?.condition],
78
+ ["$/Sqft", dp.targetProperties?.pricePerSqft],
79
+ ["Cash %", dp.acquisitionMethod?.cash],
80
+ ["Private Lending %", dp.acquisitionMethod?.privateLending],
81
+ ["Conventional %", dp.acquisitionMethod?.conventional],
82
+ ]));
83
+ }
84
+ const geo = ip.geographicFocus;
85
+ if (geo) {
86
+ lines.push("", "### Geographic Focus");
87
+ lines.push(kvSummary([
88
+ ["Primary Market", geo.primaryMarket],
89
+ ["Target Zips", Array.isArray(geo.targetZipCodes) ? geo.targetZipCodes.join(", ") : undefined],
90
+ ["Concentration", geo.marketCharacteristics?.concentration],
91
+ ["Market Dynamics", geo.marketCharacteristics?.marketDynamics],
92
+ ["Median Income", geo.marketCharacteristics?.medianIncome],
93
+ ["Property Age", geo.marketCharacteristics?.propertyAge],
94
+ ]));
95
+ }
96
+ }
97
+ // Contacts
98
+ const c = data.contacts;
99
+ if (c) {
100
+ lines.push("", "### Contacts");
101
+ lines.push(kvSummary([
102
+ ["Person", c.pname ?? c.fname],
103
+ ["Phones", Array.isArray(c.phones) && c.phones.length > 0 ? c.phones.join(", ") : undefined],
104
+ ["Emails", Array.isArray(c.emails) && c.emails.length > 0 ? c.emails.join(", ") : undefined],
105
+ ["Address", c.address],
106
+ ]));
107
+ }
108
+ const topZip = Array.isArray(data.portfolio?.topZipCodes) ? data.portfolio.topZipCodes[0] : undefined;
109
+ const actions = [
110
+ action("sfr_buyer_growth", "Acquisition trend over time", { name: args.name }),
111
+ action("plr_borrower_search", "Check if investor borrows privately", { search: args.name }),
112
+ ...(topZip ? [action("sfr_zip_detail", "Deep-dive top zip", { zipCode: topZip })] : []),
113
+ ...(topZip ? [action("sfr_search_properties", "Recent purchases in top zip", { search_type: "zip", zips: topZip })] : []),
114
+ ];
115
+ const structured = {
116
+ name: data.name,
117
+ mostActiveMsa: data.mostActiveMsa,
118
+ hasContactInfo: data.hasContactInfo,
119
+ portfolio: p ? {
120
+ propertiesCount: p.propertiesCount,
121
+ portfolioValue: p.portfolioValue,
122
+ averagePurchasePrice: p.averagePurchasePrice,
123
+ averagePropertyValue: p.averagePropertyValue,
124
+ averagePurchaseYear: p.averagePurchaseYear,
125
+ averageYearBuilt: p.averageYearBuilt,
126
+ topZipCodes: p.topZipCodes,
127
+ } : undefined,
128
+ relatedLLCs: Array.isArray(data.relatedLLCs)
129
+ ? data.relatedLLCs.map((llc) => ({ name: llc.formattedName ?? llc.name, address: llc.address }))
130
+ : undefined,
131
+ investorProfile: ip ? {
132
+ summary: ip.summary,
133
+ businessModelStrategy: ip.businessModelStrategy,
134
+ dealParameters: ip.dealParameters,
135
+ geographicFocus: ip.geographicFocus,
136
+ } : undefined,
137
+ contacts: c ? {
138
+ name: c.pname ?? c.fname,
139
+ phones: c.phones,
140
+ emails: c.emails,
141
+ address: c.address,
142
+ } : undefined,
143
+ };
144
+ return structuredResult(lines.join("\n"), structured, actions);
145
+ }
146
+ else {
147
+ lines.push("_No profile data available._");
148
+ }
149
+ return structuredResult(lines.join("\n"), {});
150
+ }
151
+ catch (err) {
152
+ if (err instanceof ApiError && err.status === 404) {
153
+ const slug = slugifyBuyerName(args.name);
154
+ return structuredResult(`## Investor Profile — ${args.name}\n\n` +
155
+ `_No profile found (slug: "${slug}"). Only major institutional investors have profiles. ` +
156
+ `Use sfr_top_buyers to discover names with profiles._`, { name: args.name, slug, error: "not_found" }, [action("sfr_top_buyers", "Find buyers with profiles", {})]);
157
+ }
158
+ throw err;
159
+ }
160
+ });
161
+ }
162
+ //# sourceMappingURL=buyerProfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buyerProfile.js","sourceRoot":"","sources":["../../../src/tools/sfr/buyerProfile.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 registerDistressSearchTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,173 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, fmtDollars } from "../formatters.js";
3
+ import { action } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ const Input = z.object({
6
+ search_type: z
7
+ .enum(["state", "city", "zip", "county", "msa"])
8
+ .optional()
9
+ .describe("Location search type — auto-inferred from provided location params when omitted"),
10
+ state: z.string().optional().describe("State abbreviation, e.g. 'FL'"),
11
+ city: z.string().optional().describe("City name, e.g. 'Miami'"),
12
+ zips: z.string().optional().describe("Comma-separated zip codes, e.g. '33101,33102'"),
13
+ county: z.string().optional().describe("County name"),
14
+ msa: z.string().optional().describe("MSA name, e.g. 'Miami-Fort Lauderdale-Pompano Beach, FL'"),
15
+ owner_name: z.string().optional().describe("Owner/entity name (partial match). Use with borrower names from PLR tools, e.g. 'EMERSON POINT LLC'"),
16
+ pre_foreclosure: z.boolean().optional().describe("Filter for pre-foreclosure properties"),
17
+ pre_foreclosure_date_min: z.string().optional().describe("Earliest pre-foreclosure date (YYYYMMDD)"),
18
+ pre_foreclosure_date_max: z.string().optional().describe("Latest pre-foreclosure date (YYYYMMDD)"),
19
+ vacant: z.boolean().optional().describe("Filter for vacant properties"),
20
+ zombie_foreclosure: z.boolean().optional().describe("Filter for zombie foreclosure properties"),
21
+ beds_min: z.coerce.number().optional().describe("Minimum bedrooms"),
22
+ beds_max: z.coerce.number().optional().describe("Maximum bedrooms"),
23
+ baths_min: z.coerce.number().optional().describe("Minimum bathrooms"),
24
+ baths_max: z.coerce.number().optional().describe("Maximum bathrooms"),
25
+ property_type: z.string().optional().describe("Property type: 'SFR', 'Condo', 'Multi-Family'"),
26
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
27
+ page_size: z.coerce.number().default(20).describe("Results per page (default 20, max 100)"),
28
+ });
29
+ export function registerDistressSearchTool(server, sfr) {
30
+ registerToolSafe(server, "sfr_distress_search", {
31
+ title: "Search distressed / pre-foreclosure properties (SFR)",
32
+ description: "Search for distressed properties: pre-foreclosure, vacant, and zombie foreclosure. " +
33
+ "Returns property details with distress indicators (owner name, foreclosure dates, vacancy). " +
34
+ "Requires at least one location filter (state, city, zip, county, or msa) — search_type is auto-inferred when omitted. " +
35
+ "Use owner_name to cross-reference PLR borrower names (from plr_lender_borrowers or plr_churned_borrowers) " +
36
+ "with distressed property owners — include a state filter from plr_borrower_loans. " +
37
+ "For individual property details use sfr_get_property." +
38
+ " Data coverage varies by market — some metros return zero distressed properties even when they exist. If no results, try narrowing to specific zip codes or use plr_negative_remarks for borrower-level distress signals instead.",
39
+ inputSchema: Input,
40
+ }, async (args) => {
41
+ // Pre-flight: API requires at least one location filter
42
+ const hasGeography = args.state || args.city || args.zips || args.county || args.msa;
43
+ if (!hasGeography) {
44
+ return structuredResult(`## Distress Search — Missing Geography\n\n` +
45
+ `_The distress API requires at least one location filter (state, city, zip, county, or msa)._ ` +
46
+ (args.owner_name
47
+ ? `To search for **${args.owner_name}**, first look up their loan states via plr_borrower_loans, then retry with a state filter.`
48
+ : `Please provide a location parameter (e.g. state='FL').`), { owner_name: args.owner_name ?? null, error: "missing_geography" }, args.owner_name
49
+ ? [action("plr_borrower_loans", "Look up borrower geography", { borrowerNames: args.owner_name })]
50
+ : []);
51
+ }
52
+ // Infer search_type from provided geography when omitted
53
+ const searchType = args.search_type
54
+ ?? (args.msa ? "msa" : undefined)
55
+ ?? (args.city ? "city" : undefined)
56
+ ?? (args.zips ? "zip" : undefined)
57
+ ?? (args.county ? "county" : undefined)
58
+ ?? (args.state ? "state" : undefined);
59
+ const query = {
60
+ search_type: searchType,
61
+ page: args.page,
62
+ page_size: Math.min(args.page_size, 100),
63
+ sort: "recording_date",
64
+ };
65
+ // Location
66
+ if (args.state)
67
+ query.state = args.state;
68
+ if (args.city)
69
+ query.city = args.city;
70
+ if (args.zips)
71
+ query.zip_codes = args.zips;
72
+ if (args.county)
73
+ query.county = args.county;
74
+ if (args.msa)
75
+ query.msa = args.msa;
76
+ if (args.owner_name)
77
+ query.owner_name = args.owner_name;
78
+ // Distress filters
79
+ if (args.pre_foreclosure !== undefined)
80
+ query.pre_foreclosure = args.pre_foreclosure;
81
+ if (args.pre_foreclosure_date_min)
82
+ query.pre_foreclosure_date_min = args.pre_foreclosure_date_min;
83
+ if (args.pre_foreclosure_date_max)
84
+ query.pre_foreclosure_date_max = args.pre_foreclosure_date_max;
85
+ if (args.vacant !== undefined)
86
+ query.vacant = args.vacant;
87
+ if (args.zombie_foreclosure !== undefined)
88
+ query.zombie_foreclosure = args.zombie_foreclosure;
89
+ // Property filters (use !== undefined to allow 0)
90
+ if (args.beds_min !== undefined)
91
+ query.beds_min = args.beds_min;
92
+ if (args.beds_max !== undefined)
93
+ query.beds_max = args.beds_max;
94
+ if (args.baths_min !== undefined)
95
+ query.baths_min = args.baths_min;
96
+ if (args.baths_max !== undefined)
97
+ query.baths_max = args.baths_max;
98
+ if (args.property_type)
99
+ query.property_type = args.property_type;
100
+ const data = await sfr.getPropertySearch(query);
101
+ const items = Array.isArray(data?.data) ? data.data : [];
102
+ const total = data?.total ?? items.length;
103
+ const loc = args.city ?? args.zips ?? args.county ?? args.state ?? args.msa ?? "All";
104
+ const lines = [
105
+ `## Distressed Properties — ${loc}`,
106
+ `**Total:** ${typeof total === "number" ? total.toLocaleString() : total} | **Page:** ${args.page} | **Showing:** ${items.length}`,
107
+ "",
108
+ ];
109
+ if (items.length > 0) {
110
+ const rows = items.slice(0, 25).map((p) => [
111
+ p.address?.formatted_street_address ?? "—",
112
+ p.address?.city ?? "—",
113
+ p.address?.zip_code ?? "—",
114
+ p.structure?.beds_count ?? "—",
115
+ p.pre_foreclosure?.flag ? "Yes" : "No",
116
+ p.pre_foreclosure?.recording_date ?? "—",
117
+ p.vacant ? "Yes" : "No",
118
+ fmtDollars(p.valuation?.value ?? p.assessments?.assessed_value),
119
+ ]);
120
+ lines.push(markdownTable(["Address", "City", "ZIP", "Beds", "Pre-Forecl.", "Fcl. Date", "Vacant", "Value"], rows));
121
+ }
122
+ else {
123
+ lines.push("_No distressed properties found matching criteria._ " +
124
+ "Data coverage varies by market. Try narrowing to specific zip codes, or " +
125
+ "use plr_negative_remarks for borrower-level distress signals (foreclosures, defaults).");
126
+ }
127
+ // Slim structured items
128
+ const slimItems = items.slice(0, 25).map((p) => ({
129
+ address: p.address?.formatted_street_address,
130
+ city: p.address?.city,
131
+ state: p.address?.state,
132
+ zip: p.address?.zip_code,
133
+ beds: p.structure?.beds_count || undefined,
134
+ baths: p.structure?.baths || undefined,
135
+ yearBuilt: p.structure?.year_built || undefined,
136
+ propertyType: p.property_class_description || undefined,
137
+ preForeclosure: p.pre_foreclosure?.flag ?? false,
138
+ preForeclosureDate: p.pre_foreclosure?.recording_date || undefined,
139
+ preForeclosureReason: p.pre_foreclosure?.reason || undefined,
140
+ vacant: p.vacant ?? false,
141
+ estimatedValue: p.valuation?.value || undefined,
142
+ assessedValue: p.assessments?.assessed_value || undefined,
143
+ ownerName: p.owner?.name || undefined,
144
+ lastSaleDate: p.last_sale?.date || undefined,
145
+ lastSalePrice: p.last_sale?.price || undefined,
146
+ }));
147
+ const firstAddr = items[0]
148
+ ? `${items[0].address?.formatted_street_address}, ${items[0].address?.city}, ${items[0].address?.state} ${items[0].address?.zip_code}`
149
+ : undefined;
150
+ // sfr_top_buyers supports state/city/zip/msa but NOT county
151
+ const topBuyersSupported = searchType !== "county";
152
+ const topBuyersArgs = { search_type: searchType };
153
+ if (args.state)
154
+ topBuyersArgs.state = args.state;
155
+ if (args.city)
156
+ topBuyersArgs.city = args.city;
157
+ if (args.zips)
158
+ topBuyersArgs.zips = args.zips;
159
+ if (args.msa)
160
+ topBuyersArgs.msa = args.msa;
161
+ const actions = [
162
+ ...(firstAddr ? [action("sfr_get_property", "Full details on first result", { address: firstAddr })] : []),
163
+ action("plr_borrower_search", "Find private borrowers in this area", {
164
+ ...(args.state ? { state: args.state } : {}),
165
+ }),
166
+ ...(topBuyersSupported
167
+ ? [action("sfr_top_buyers", "Who's buying distressed properties here", topBuyersArgs)]
168
+ : []),
169
+ ];
170
+ return structuredResult(lines.join("\n"), { items: slimItems, total, page: args.page }, actions);
171
+ });
172
+ }
173
+ //# sourceMappingURL=distressSearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distressSearch.js","sourceRoot":"","sources":["../../../src/tools/sfr/distressSearch.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 registerFlipActivityTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, argsToQuery } 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"]).default("msa")
8
+ .describe("Location search type (default: msa)"),
9
+ msa: z.string().optional().describe("MSA name, e.g. 'Phoenix-Mesa-Chandler, AZ'"),
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"),
13
+ buyer_name: z.string().optional().describe("Filter by buyer/flipper name"),
14
+ is_foreclosure: z.boolean().optional().describe("Filter for foreclosure sales only"),
15
+ is_reo_sale: z.boolean().optional().describe("Filter for REO (bank-owned) sales only"),
16
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
17
+ page_size: z.coerce.number().default(20).describe("Results per page (default 20, max 100)"),
18
+ });
19
+ export function registerFlipActivityTool(server, sfr) {
20
+ registerToolSafe(server, "sfr_flip_activity", {
21
+ title: "Recent flip transactions (SFR)",
22
+ description: "Browse individual flip transactions (buy-and-resell) in a market. " +
23
+ "Returns original buyer, flipper, purchase/sale prices, hold time, and profit. " +
24
+ "Supports buyer_name filtering (partial match, case-insensitive) for a specific buyer/flipper strategy. " +
25
+ "For iBuyer margin analysis (e.g. 'how are Opendoor margins trending'), use buyer_name='OPENDOOR' — partial match finds all Opendoor entities. " +
26
+ "Chain with sfr_buyer_profile to get relatedLLCs and search flips across all related entities. " +
27
+ "Can filter for foreclosure sales (is_foreclosure) or REO/bank-owned sales (is_reo_sale). " +
28
+ "For aggregate flip distributions (hold time buckets, margin ranges), use sfr_flip_stats.",
29
+ inputSchema: Input,
30
+ }, async (args) => {
31
+ const query = argsToQuery(args);
32
+ let effectiveMsa = args.msa;
33
+ let resolutionMethod = "exact";
34
+ const data = await sfr.getFlips(query);
35
+ let items = Array.isArray(data) ? data : data?.data ?? [];
36
+ if (items.length === 0 && args.search_type === "msa" && args.msa) {
37
+ resolutionMethod = "none";
38
+ const resolved = await resolveCanonicalMsa(sfr, { requestedMsa: args.msa });
39
+ if (resolved.resolvedMsa
40
+ && resolved.resolvedMsa.toLowerCase() !== args.msa.trim().toLowerCase()) {
41
+ effectiveMsa = resolved.resolvedMsa;
42
+ const retryData = await sfr.getFlips({ ...query, msa: effectiveMsa });
43
+ items = Array.isArray(retryData) ? retryData : retryData?.data ?? [];
44
+ if (items.length > 0)
45
+ resolutionMethod = "fallback";
46
+ }
47
+ }
48
+ const loc = (effectiveMsa ?? args.msa) ?? args.city ?? args.zips ?? args.state ?? "All";
49
+ const lines = [
50
+ `## Flip Transactions — ${loc}`,
51
+ `**Showing:** ${items.length} flips`,
52
+ "",
53
+ ];
54
+ if (resolutionMethod === "fallback") {
55
+ lines.push(`_MSA normalized: '${args.msa}' -> '${effectiveMsa}'_`, "");
56
+ }
57
+ if (items.length > 0) {
58
+ const fmt$ = (v) => v != null ? `$${Number(v).toLocaleString()}` : "—";
59
+ const rows = items.slice(0, 25).map((f) => [
60
+ f.address ? `${f.address}, ${f.city ?? ""}` : "—",
61
+ f.prevBuyer ?? "—",
62
+ fmt$(f.prevSaleValue),
63
+ fmt$(f.saleValue),
64
+ f.daysDiff != null ? `${f.daysDiff}d` : "—",
65
+ fmt$(f.saleAmountDiff),
66
+ ]);
67
+ lines.push(markdownTable(["Address", "Flipper", "Buy Price", "Sell Price", "Hold", "Profit"], rows));
68
+ }
69
+ else {
70
+ lines.push("_No flip transactions found._ " +
71
+ (args.buyer_name ? `Buyer name '${args.buyer_name}' may not match — try a shorter/partial name. ` : "") +
72
+ "Try widening the date range or checking the MSA/city name.");
73
+ }
74
+ // Slim items — raw API has 40+ fields with boolean flags, URL slugs, lat/lng
75
+ const slimItems = items.slice(0, 25).map((f) => ({
76
+ address: f.address,
77
+ city: f.city,
78
+ zip: f.zipCode,
79
+ state: f.state,
80
+ propertyType: f.landUseCode,
81
+ sqft: f.buildingArea || undefined,
82
+ yearBuilt: f.yearBuilt || undefined,
83
+ prevBuyer: f.prevBuyer,
84
+ prevSaleValue: f.prevSaleValue,
85
+ prevSaleDate: f.prevSaleDate,
86
+ buyerName: f.buyerName,
87
+ saleValue: f.saleValue,
88
+ saleDate: f.saleDate,
89
+ daysDiff: f.daysDiff,
90
+ grossMargin: f.grossMargin ?? f.saleAmountDiff,
91
+ transactionType: f.transactionType,
92
+ isCashPurchase: f.isCashPurchase || undefined,
93
+ isCorporatePurchase: f.isCorporatePurchase || undefined,
94
+ isNewConstruction: f.isNewConstruction || undefined,
95
+ isForeclosure: f.isForeclosure || undefined,
96
+ flipMortgage: f.flipMortgage || undefined,
97
+ msa: f.msa,
98
+ }));
99
+ const firstAddr = items[0] ? `${items[0].address}, ${items[0].city}, ${items[0].state} ${items[0].zipCode}` : undefined;
100
+ const firstFlipper = items[0]?.prevBuyer;
101
+ const locArgs = locationArgs(args);
102
+ const actions = [
103
+ ...(firstAddr ? [action("sfr_get_property", "Details on first flip", { address: firstAddr })] : []),
104
+ ...(firstFlipper ? [action("sfr_buyer_profile", "Profile of flipper", { name: firstFlipper })] : []),
105
+ action("sfr_flip_stats", "Flip profitability trends", locArgs),
106
+ ];
107
+ return structuredResult(lines.join("\n"), { items: slimItems, total: items.length, page: args.page }, actions);
108
+ });
109
+ }
110
+ //# sourceMappingURL=flipActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flipActivity.js","sourceRoot":"","sources":["../../../src/tools/sfr/flipActivity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACrB,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;SAChE,QAAQ,CAAC,qCAAqC,CAAC;IAClD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;IACjF,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IACtE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;IAChG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2BAA2B,CAAC;IACjE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;IAC1E,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;IACpF,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;IACtF,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;CAC5F,CAAC,CAAC;AAEH,MAAM,UAAU,wBAAwB,CAAC,MAAiB,EAAE,GAAc;IACxE,gBAAgB,CAAC,MAAM,EACrB,mBAAmB,EACnB;QACE,KAAK,EAAE,gCAAgC;QACvC,WAAW,EACT,oEAAoE;YACpE,gFAAgF;YAChF,yGAAyG;YACzG,gJAAgJ;YAChJ,gGAAgG;YAChG,2FAA2F;YAC3F,0FAA0F;QAC5F,WAAW,EAAE,KAAK;KACnB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACX,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAEhC,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC;QAC5B,IAAI,gBAAgB,GAAkC,OAAO,CAAC;QAE9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAQ,CAAC;QAC9C,IAAI,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;QAE1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACjE,gBAAgB,GAAG,MAAM,CAAC;YAC1B,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YAC5E,IACE,QAAQ,CAAC,WAAW;mBACjB,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EACvE,CAAC;gBACD,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC;gBACpC,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,CAAQ,CAAC;gBAC7E,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC;gBACrE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;oBAAE,gBAAgB,GAAG,UAAU,CAAC;YACtD,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;QACxF,MAAM,KAAK,GAAG;YACZ,0BAA0B,GAAG,EAAE;YAC/B,gBAAgB,KAAK,CAAC,MAAM,QAAQ;YACpC,EAAE;SACH,CAAC;QACF,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;YACpC,KAAK,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,GAAG,SAAS,YAAY,IAAI,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;YAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC;gBAC9C,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG;gBACjD,CAAC,CAAC,SAAS,IAAI,GAAG;gBAClB,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;gBACrB,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;gBACjB,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,GAAG;gBAC3C,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC;aACvB,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,aAAa,CACtB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EACnE,IAAI,CACL,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CACR,gCAAgC;gBAChC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,UAAU,gDAAgD,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvG,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QAED,6EAA6E;QAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,CAAC,CAAC,OAAO;YACd,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,YAAY,EAAE,CAAC,CAAC,WAAW;YAC3B,IAAI,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS;YACjC,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS;YACnC,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,cAAc;YAC9C,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,SAAS;YAC7C,mBAAmB,EAAE,CAAC,CAAC,mBAAmB,IAAI,SAAS;YACvD,iBAAiB,EAAE,CAAC,CAAC,iBAAiB,IAAI,SAAS;YACnD,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,SAAS;YAC3C,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,SAAS;YACzC,GAAG,EAAE,CAAC,CAAC,GAAG;SACX,CAAC,CAAC,CAAC;QAEJ,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACxH,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;QACzC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,OAAO,GAAG;YACd,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,uBAAuB,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACnG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACpG,MAAM,CAAC,gBAAgB,EAAE,2BAA2B,EAAE,OAAO,CAAC;SAC/D,CAAC;QACF,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACnH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -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 registerFlipStatsTool(server: McpServer, sfr: SfrClient): void;