@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,75 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, truncatedJson, fmtDollars } from "../formatters.js";
3
+ import { registerToolSafe } from "../registerToolSafe.js";
4
+ import { autoFillPlrDates } from "../dateHelper.js";
5
+ import { action } from "../nextActions.js";
6
+ const Input = z.object({
7
+ search: z.string().optional().describe("Search text for lender name (matches both short 'lender' and display 'clean_name')"),
8
+ states: z.string().optional().describe("Comma-separated state abbreviations"),
9
+ msas: z.string().optional().describe("Semicolon-separated MSA names (use ; because MSA names contain commas), e.g. 'Phoenix-Mesa-Chandler, AZ; Dallas-Fort Worth-Arlington, TX'"),
10
+ startDate: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
11
+ endDate: z.string().optional().describe("End date"),
12
+ loanTypes: z.string().optional().describe("Comma-separated: 'Bridge', 'DSCR', 'Unknown'"),
13
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
14
+ pageSize: z.coerce.number().default(25).describe("Results per page (default 25, max 25)"),
15
+ });
16
+ export function registerTopLendersTool(server, plr) {
17
+ registerToolSafe(server, "plr_top_lenders", {
18
+ title: "Rank lenders by DOLLAR VOLUME — with geographic filters (PLR)",
19
+ description: "Rank private lenders by total dollar volume originated, with geographic filters (state/MSA). " +
20
+ "Use for market share analysis: 'top lenders in FL', 'lender market share in Texas', or 'biggest lenders in Phoenix MSA'. " +
21
+ "Different from plr_lender_rankings which ranks by loan count and has NO geographic filters. " +
22
+ "Returns lender names, origination volume, loan count, and HQ location. " +
23
+ "Filter by loanTypes ('Bridge', 'DSCR', 'Unknown') for portfolio/DSCR-specific market share.",
24
+ inputSchema: Input,
25
+ }, async (args) => {
26
+ const body = {
27
+ page: args.page,
28
+ page_size: Math.min(args.pageSize, 25),
29
+ };
30
+ if (args.search)
31
+ body.search = args.search;
32
+ if (args.states)
33
+ body.states = args.states.split(",").map((s) => s.trim());
34
+ if (args.msas)
35
+ body.msas = args.msas.split(";").map((s) => s.trim());
36
+ if (args.startDate)
37
+ body.start_date = args.startDate;
38
+ if (args.endDate)
39
+ body.end_date = args.endDate;
40
+ if (args.loanTypes)
41
+ body.loan_types = args.loanTypes.split(",").map((s) => s.trim());
42
+ autoFillPlrDates(body);
43
+ const data = await plr.topLendersByVolume(body);
44
+ const items = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
45
+ const total = data?.count ?? items.length;
46
+ const lines = [
47
+ `## Top Lenders by Volume`,
48
+ `**Total:** ${total} | **Page:** ${args.page}`,
49
+ "",
50
+ ];
51
+ if (items.length > 0) {
52
+ const rows = items.slice(0, 25).map((l, i) => [
53
+ i + 1 + (args.page - 1) * args.pageSize,
54
+ l.clean_name ?? l.lender ?? "—",
55
+ fmtDollars(l.origination_volume ?? l.total_amount),
56
+ l.loan_count != null ? Number(l.loan_count).toLocaleString() : "—",
57
+ l.headquarters_location ?? "—",
58
+ ]);
59
+ lines.push(markdownTable(["#", "Lender", "Volume", "Loans", "HQ"], rows));
60
+ }
61
+ else {
62
+ lines.push("```json");
63
+ lines.push(truncatedJson(data, 3000));
64
+ lines.push("```");
65
+ }
66
+ const firstName = items[0]?.lender ?? items[0]?.clean_name;
67
+ const actions = [
68
+ ...(firstName ? [action("plr_lender_borrowers", "Borrowers of top lender", { lenders: firstName })] : []),
69
+ ...(firstName ? [action("plr_churned_borrowers", "Churned borrowers from top lender", { lenders: firstName })] : []),
70
+ action("plr_lender_rankings", "Rank by loan count (nationwide)", {}),
71
+ ];
72
+ return structuredResult(lines.join("\n"), { items, total, page: args.page }, actions);
73
+ });
74
+ }
75
+ //# sourceMappingURL=topLenders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topLenders.js","sourceRoot":"","sources":["../../../src/tools/plr/topLenders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PlrClient } from "../../services/plr.js";
3
+ export declare function registerTransactionHistoryTool(server: McpServer, plr: PlrClient): void;
@@ -0,0 +1,74 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
3
+ import { registerToolSafe } from "../registerToolSafe.js";
4
+ import { action } from "../nextActions.js";
5
+ const Input = z.object({
6
+ propertyAddress: z.string().describe("Full property address, e.g. '123 Main St, Phoenix, AZ 85004'"),
7
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
8
+ pageSize: z.coerce.number().default(10).describe("Results per page (default 10, max 25)"),
9
+ });
10
+ export function registerTransactionHistoryTool(server, plr) {
11
+ registerToolSafe(server, "plr_transaction_history", {
12
+ title: "Property private lending history (PLR)",
13
+ description: "Get the private lending transaction history for a property address. " +
14
+ "Returns all recorded private loans (bridge/DSCR) with lender names, amounts, " +
15
+ "recording dates, and loan types. Includes property details (AVM, owner info). " +
16
+ "Complements sfr_property_transactions which shows deeds/sales.",
17
+ inputSchema: Input,
18
+ }, async (args) => {
19
+ const body = {
20
+ property_address: args.propertyAddress,
21
+ page: args.page,
22
+ page_size: Math.min(args.pageSize, 25),
23
+ };
24
+ const data = await plr.transactionHistory(body);
25
+ const items = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
26
+ const total = data?.count ?? items.length;
27
+ const lines = [
28
+ `## Private Lending History — ${args.propertyAddress}`,
29
+ `**Total:** ${total} transactions`,
30
+ "",
31
+ ];
32
+ if (items.length > 0) {
33
+ const fmt$ = (v) => v != null && Number(v) > 0 ? `$${Number(v).toLocaleString()}` : "—";
34
+ const rows = items.slice(0, 20).map((t) => [
35
+ t.recording_date ?? "—",
36
+ t.tx_type ?? t.transaction_type_clean ?? "—",
37
+ fmt$(t.sale_amt),
38
+ t.buyer_borrower1_name ?? "—",
39
+ t.seller1_name ?? "—",
40
+ t.lender_clean ?? t.first_mtg_lender_name ?? "—",
41
+ fmt$(t.first_mtg_amt),
42
+ ]);
43
+ lines.push(markdownTable(["Date", "Type", "Sale Price", "Buyer", "Seller", "Lender", "Mortgage"], rows));
44
+ if (data.property_details) {
45
+ const pd = data.property_details;
46
+ lines.push("", "### Property Details");
47
+ const detailLines = [
48
+ pd.avm_value ? `**AVM:** $${Number(pd.avm_value).toLocaleString()}` : null,
49
+ pd.market_value ? `**Market Value:** $${Number(pd.market_value).toLocaleString()}` : null,
50
+ pd.owner ? `**Owner:** ${pd.owner}` : null,
51
+ pd.property_type ? `**Type:** ${pd.property_type}` : null,
52
+ pd.beds ? `**Beds:** ${pd.beds}` : null,
53
+ pd.total_sqft ? `**Sqft:** ${pd.total_sqft}` : null,
54
+ ].filter(Boolean);
55
+ lines.push(detailLines.join(" | "));
56
+ }
57
+ }
58
+ else {
59
+ lines.push("_No private lending transactions found for this property._");
60
+ if (data && typeof data === "object") {
61
+ lines.push("```json");
62
+ lines.push(truncatedJson(data, 2000));
63
+ lines.push("```");
64
+ }
65
+ }
66
+ const actions = [
67
+ action("sfr_get_property", "Full property details & valuation", { address: args.propertyAddress }),
68
+ action("plr_loans_nearby", "Other loans in the area", { propertyAddress: args.propertyAddress }),
69
+ action("sfr_property_transactions", "Deed & sale history", { address: args.propertyAddress }),
70
+ ];
71
+ return structuredResult(lines.join("\n"), { items, total, page: args.page }, actions);
72
+ });
73
+ }
74
+ //# sourceMappingURL=transactionHistory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactionHistory.js","sourceRoot":"","sources":["../../../src/tools/plr/transactionHistory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Register example prompts that guide users through common workflows.
4
+ * Prompts are discoverable by MCP clients and serve as entry points.
5
+ * Only registers prompts for APIs the user has configured.
6
+ */
7
+ export declare function registerPrompts(server: McpServer, hasSfr: boolean, hasPlr: boolean): void;
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Register example prompts that guide users through common workflows.
4
+ * Prompts are discoverable by MCP clients and serve as entry points.
5
+ * Only registers prompts for APIs the user has configured.
6
+ */
7
+ export function registerPrompts(server, hasSfr, hasPlr) {
8
+ if (hasSfr) {
9
+ server.registerPrompt("market-screen", {
10
+ title: "Screen markets by investment criteria",
11
+ description: "Find the best zip codes for SFR investing by yield, rent, home value, " +
12
+ "and institutional ownership. Then deep-dive into top picks.",
13
+ argsSchema: {
14
+ sortBy: z
15
+ .enum(["gross_yield", "median_rent", "median_home_value", "inst_own_percentage"])
16
+ .default("gross_yield")
17
+ .describe("Primary sort metric (prefix with - for descending)"),
18
+ count: z
19
+ .string()
20
+ .default("10")
21
+ .describe("Number of top zips to analyze"),
22
+ },
23
+ }, async (args) => ({
24
+ messages: [
25
+ {
26
+ role: "user",
27
+ content: {
28
+ type: "text",
29
+ text: [
30
+ `Find the top ${args.count} zip codes for SFR investing, sorted by ${args.sortBy} (descending).`,
31
+ "",
32
+ "For each top zip, show:",
33
+ "1. Gross yield, median home value, median rent, institutional ownership",
34
+ "2. Rental market stats (median, p25, p75)",
35
+ "3. Top buyers active in that zip",
36
+ "",
37
+ "Then recommend the 3 best zips and explain why.",
38
+ ].join("\n"),
39
+ },
40
+ },
41
+ ],
42
+ }));
43
+ server.registerPrompt("buyer-research", {
44
+ title: "Research a property buyer/investor",
45
+ description: "Get a comprehensive view of a buyer: portfolio, strategy, growth trend, and contacts.",
46
+ argsSchema: {
47
+ name: z.string().describe("Buyer/investor name, e.g. 'Invitation Homes'"),
48
+ },
49
+ }, async (args) => ({
50
+ messages: [
51
+ {
52
+ role: "user",
53
+ content: {
54
+ type: "text",
55
+ text: [
56
+ `Research the property investor "${args.name}":`,
57
+ "",
58
+ "1. Pull their full profile (portfolio stats, strategy, geographic focus)",
59
+ "2. Show their acquisition growth trend over the past 2 years",
60
+ "3. Identify what markets they're most active in",
61
+ "4. Summarize their investment strategy and deal parameters",
62
+ ].join("\n"),
63
+ },
64
+ },
65
+ ],
66
+ }));
67
+ server.registerPrompt("property-analysis", {
68
+ title: "Analyze a property for investment",
69
+ description: "Evaluate a specific property: transaction history, rental comps, and market context.",
70
+ argsSchema: {
71
+ address: z.string().describe("Full property address, e.g. '123 Main St, Phoenix, AZ 85004'"),
72
+ },
73
+ }, async (args) => ({
74
+ messages: [
75
+ {
76
+ role: "user",
77
+ content: {
78
+ type: "text",
79
+ text: [
80
+ `Analyze this property for SFR investment: ${args.address}`,
81
+ "",
82
+ "1. Look up the property details and transaction history",
83
+ "2. Find rental comparables nearby to estimate fair market rent",
84
+ "3. Get rental stats for the zip code",
85
+ "4. Get zip code detail for broader market context",
86
+ "5. Check who the top buyers are in this area",
87
+ "",
88
+ "Then give an investment assessment: estimated rent, yield, and market quality.",
89
+ ].join("\n"),
90
+ },
91
+ },
92
+ ],
93
+ }));
94
+ }
95
+ if (hasPlr) {
96
+ server.registerPrompt("lending-overview", {
97
+ title: "Private lending market overview",
98
+ description: "Get a comprehensive view of the private lending market: volume trends, " +
99
+ "top lenders, top borrowers, and MSA rankings.",
100
+ }, async () => ({
101
+ messages: [
102
+ {
103
+ role: "user",
104
+ content: {
105
+ type: "text",
106
+ text: [
107
+ "Give me a comprehensive overview of the private lending market:",
108
+ "",
109
+ "1. Start with the portfolio summary (total loans, volume, lender/borrower counts)",
110
+ "2. Show quarterly trends for the past 2 years",
111
+ "3. Rank the top 10 MSAs by lending volume",
112
+ "4. List the top 10 lenders and top 10 borrowers",
113
+ "",
114
+ "Highlight any notable trends (growth, contraction, shifts in loan types).",
115
+ ].join("\n"),
116
+ },
117
+ },
118
+ ],
119
+ }));
120
+ server.registerPrompt("borrower-outreach", {
121
+ title: "Build a borrower outreach list",
122
+ description: "Find active private lending borrowers matching criteria and get their contact info.",
123
+ argsSchema: {
124
+ states: z.string().optional().describe("Target states, e.g. 'AZ,TX'"),
125
+ loanType: z
126
+ .enum(["Bridge", "DSCR", "Unknown"])
127
+ .optional()
128
+ .describe("Loan type filter"),
129
+ minLoans: z.string().default("3").describe("Minimum loan count"),
130
+ },
131
+ }, async (args) => ({
132
+ messages: [
133
+ {
134
+ role: "user",
135
+ content: {
136
+ type: "text",
137
+ text: [
138
+ "Build a targeted borrower outreach list with these criteria:",
139
+ args.states ? `- States: ${args.states}` : "- All states",
140
+ args.loanType ? `- Loan type: ${args.loanType}` : "- All loan types",
141
+ `- Minimum ${args.minLoans} loans`,
142
+ "- Must have contact info (phone or email)",
143
+ "",
144
+ "For each borrower, show:",
145
+ "1. Name, loan count, total volume",
146
+ "2. Active states and loan types",
147
+ "3. Contact information (phone, email)",
148
+ "",
149
+ "Sort by loan count descending.",
150
+ ].join("\n"),
151
+ },
152
+ },
153
+ ],
154
+ }));
155
+ }
156
+ }
157
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/tools/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { z } from "zod";
4
+ type AnySchema = z.ZodTypeAny;
5
+ type ZodRawShapeCompat = Record<string, z.ZodTypeAny>;
6
+ type ToolConfig<InputArgs extends undefined | ZodRawShapeCompat | AnySchema> = {
7
+ title?: string;
8
+ description?: string;
9
+ inputSchema?: InputArgs;
10
+ outputSchema?: ZodRawShapeCompat | AnySchema;
11
+ annotations?: ToolAnnotations;
12
+ _meta?: Record<string, unknown>;
13
+ };
14
+ type ToolCallbackResult = {
15
+ content: {
16
+ type: "text";
17
+ text: string;
18
+ }[];
19
+ structuredContent?: Record<string, unknown>;
20
+ isError?: boolean;
21
+ };
22
+ /**
23
+ * Wrapper around `server.registerTool` that adds:
24
+ * - readOnlyHint + idempotentHint annotations by default
25
+ * - automatic try/catch → handleToolError
26
+ * - latency logging to stderr (>100ms only)
27
+ */
28
+ export declare function registerToolSafe<InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined>(server: McpServer, toolName: string, config: ToolConfig<InputArgs>, cb: (args: any) => Promise<ToolCallbackResult>): void;
29
+ export {};
@@ -0,0 +1,36 @@
1
+ import { handleToolError } from "./formatters.js";
2
+ /**
3
+ * Wrapper around `server.registerTool` that adds:
4
+ * - readOnlyHint + idempotentHint annotations by default
5
+ * - automatic try/catch → handleToolError
6
+ * - latency logging to stderr (>100ms only)
7
+ */
8
+ export function registerToolSafe(server, toolName, config,
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ cb) {
11
+ const mergedConfig = {
12
+ ...config,
13
+ annotations: {
14
+ readOnlyHint: true,
15
+ idempotentHint: true,
16
+ ...config.annotations,
17
+ },
18
+ };
19
+ server.registerTool(toolName, mergedConfig, async (args) => {
20
+ const t0 = performance.now();
21
+ try {
22
+ const result = await cb(args);
23
+ return result;
24
+ }
25
+ catch (err) {
26
+ return handleToolError(err, toolName);
27
+ }
28
+ finally {
29
+ const elapsed = performance.now() - t0;
30
+ if (elapsed > 100) {
31
+ process.stderr.write(`[sfra-mcp] ${toolName}: ${Math.round(elapsed)}ms\n`);
32
+ }
33
+ }
34
+ });
35
+ }
36
+ //# sourceMappingURL=registerToolSafe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registerToolSafe.js","sourceRoot":"","sources":["../../src/tools/registerToolSafe.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAsBlD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAG9B,MAAiB,EACjB,QAAgB,EAChB,MAA6B;AAC7B,8DAA8D;AAC9D,EAA8C;IAE9C,MAAM,YAAY,GAAG;QACnB,GAAG,MAAM;QACT,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,cAAc,EAAE,IAAI;YACpB,GAAG,MAAM,CAAC,WAAW;SACtB;KACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,QAAQ,EACR,YAAyD,EACzD,KAAK,EAAE,IAAS,EAAE,EAAE;QAClB,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YACvC,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;gBAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,QAAQ,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC;IACH,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 registerActivityHighlightsTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, kvSummary, markdownTable, argsToQuery } from "../formatters.js";
3
+ import { action, locationArgs } from "../nextActions.js";
4
+ import { registerToolSafe } from "../registerToolSafe.js";
5
+ import { autoFillDateRange } from "../dateHelper.js";
6
+ const Input = z.object({
7
+ search_type: z.enum(["state", "city", "zip", "msa", "address"]).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
+ sales_date_min: z.string().optional().describe("Start date, e.g. '2024-01-01'"),
13
+ sales_date_max: z.string().optional().describe("End date"),
14
+ });
15
+ export function registerActivityHighlightsTool(server, sfr) {
16
+ registerToolSafe(server, "sfr_activity_highlights", {
17
+ title: "Quick market summary — transaction count + top 10 buyers (SFR)",
18
+ description: "Quick market count: total transactions plus top 10 buyers in a geography. " +
19
+ "A lightweight preview — use sfr_top_buyers for the full buyer list with contacts/profiles. " +
20
+ "Best for 'how many transactions in [location]?' before deciding to drill deeper. " +
21
+ "Supports any geography: state, city, zip, MSA, or address.",
22
+ inputSchema: Input,
23
+ }, async (args) => {
24
+ const query = argsToQuery(args);
25
+ autoFillDateRange(query);
26
+ // Fetch count and top buyers in parallel
27
+ const [countData, topBuyersData] = await Promise.all([
28
+ sfr.getActivityCount(query),
29
+ sfr.getTopBuyers({ ...query, page_size: 10 }),
30
+ ]);
31
+ const count = typeof countData === "number" ? countData : countData?.count ?? countData;
32
+ const topBuyers = Array.isArray(topBuyersData)
33
+ ? topBuyersData
34
+ : topBuyersData?.data ?? [];
35
+ const loc = args.zips ?? args.city ?? args.msa ?? args.state ?? "All";
36
+ const lines = [
37
+ `## Activity Highlights — ${loc}`,
38
+ "",
39
+ kvSummary([
40
+ ["Total Transactions", count != null ? Number(count).toLocaleString() : undefined],
41
+ ]),
42
+ "",
43
+ ];
44
+ if (topBuyers.length > 0) {
45
+ lines.push("### Top Buyers");
46
+ const sorted = [...topBuyers].sort((a, b) => (b.acquisitions ?? b.count ?? 0) - (a.acquisitions ?? a.count ?? 0));
47
+ const rows = sorted.slice(0, 10).map((b, i) => [
48
+ i + 1,
49
+ b.name ?? b.buyer_name ?? "—",
50
+ b.acquisitions ?? b.count ?? "—",
51
+ ]);
52
+ lines.push(markdownTable(["#", "Buyer", "Acquisitions"], rows));
53
+ }
54
+ // Slim topBuyers — strip URL slug hashes, keep only key fields
55
+ const slimBuyers = topBuyers.slice(0, 15).map((b) => ({
56
+ name: b.name ?? b.buyer_name,
57
+ acquisitions: b.acquisitions ?? b.count,
58
+ hasProfile: b.hasProfilePage || undefined,
59
+ hasContact: b.hasContactInfo || undefined,
60
+ }));
61
+ const locArgs = locationArgs(args);
62
+ const actions = [
63
+ action("sfr_top_buyers", "Full buyer list with profiles & contacts", locArgs),
64
+ action("sfr_search_properties", "Browse individual transactions", locArgs),
65
+ action("sfr_rental_stats", "Rental market for this area", locArgs),
66
+ ];
67
+ return structuredResult(lines.join("\n"), { count, topBuyers: slimBuyers, page: 1 }, actions);
68
+ });
69
+ }
70
+ //# sourceMappingURL=activityHighlights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activityHighlights.js","sourceRoot":"","sources":["../../../src/tools/sfr/activityHighlights.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 registerBestBuyersTool(server: McpServer, sfr: SfrClient): void;
@@ -0,0 +1,60 @@
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
+ const Input = z.object({
6
+ address: z.string().describe("Full property address, e.g. '123 Main St, Phoenix, AZ 85004'"),
7
+ });
8
+ export function registerBestBuyersTool(server, sfr) {
9
+ registerToolSafe(server, "sfr_best_buyers", {
10
+ title: "Best buyers for a property (SFR)",
11
+ description: "Find the most likely buyers for a specific property based on their acquisition patterns. " +
12
+ "Returns investors who have bought similar properties in the same area. " +
13
+ "Ideal for sellers, wholesalers, or agents matching properties to active buyers.",
14
+ inputSchema: Input,
15
+ }, async (args) => {
16
+ const data = await sfr.getBestBuyers(args.address);
17
+ const buyers = data?.data ?? data?.buyers ?? (Array.isArray(data) ? data : []);
18
+ const lines = [
19
+ `## Best Buyers — ${args.address}`,
20
+ `**Matches:** ${buyers.length} investors`,
21
+ "",
22
+ ];
23
+ if (buyers.length > 0) {
24
+ // API returns {name, matchScore, totalAcquisitions, recentPurchasesCount, matchReasons}
25
+ const rows = buyers.slice(0, 25).map((b, i) => [
26
+ i + 1,
27
+ b.name ?? b.buyer_name ?? "—",
28
+ b.totalAcquisitions ?? b.acquisitions ?? "—",
29
+ b.recentPurchasesCount ?? "—",
30
+ b.matchScore != null ? `${b.matchScore}%` : "—",
31
+ Array.isArray(b.matchReasons) ? b.matchReasons[0] ?? "—" : "—",
32
+ ]);
33
+ lines.push(markdownTable(["#", "Buyer", "Total Acq.", "Recent", "Match", "Reason"], rows));
34
+ }
35
+ else {
36
+ lines.push("```json");
37
+ lines.push(truncatedJson(data, 3000));
38
+ lines.push("```");
39
+ }
40
+ // Slim buyers — keep match-specific fields, strip URL slugs
41
+ const slimBuyers = buyers.slice(0, 25).map((b) => ({
42
+ name: b.name ?? b.buyer_name,
43
+ totalAcquisitions: b.totalAcquisitions ?? b.acquisitions,
44
+ recentPurchasesCount: b.recentPurchasesCount,
45
+ purchasesWithinQuarterMile: b.purchasesWithinQuarterMile || undefined,
46
+ matchScore: b.matchScore,
47
+ matchReasons: b.matchReasons,
48
+ hasProfile: b.hasProfilePage || undefined,
49
+ hasContact: b.hasContactInfo || undefined,
50
+ }));
51
+ const firstName = buyers[0]?.name ?? buyers[0]?.buyer_name;
52
+ const actions = [
53
+ ...(firstName ? [action("sfr_buyer_profile", "Profile top match", { name: firstName })] : []),
54
+ ...(firstName ? [action("sfr_buyer_growth", "Growth trend for top match", { name: firstName })] : []),
55
+ action("sfr_get_property", "Full property details", { address: args.address }),
56
+ ];
57
+ return structuredResult(lines.join("\n"), { items: slimBuyers, total: buyers.length, page: 1 }, actions);
58
+ });
59
+ }
60
+ //# sourceMappingURL=bestBuyers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bestBuyers.js","sourceRoot":"","sources":["../../../src/tools/sfr/bestBuyers.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 registerBuyerGrowthTool(server: McpServer, sfr: SfrClient): void;