@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,73 @@
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
+ borrowerNames: z.string().describe("Comma-separated borrower entity names — use EXACT names from search results (typically UPPERCASE, e.g. 'VAPA INVESTMENTS LLC')"),
7
+ state: z.string().optional().describe("Optional state filter"),
8
+ page: z.coerce.number().default(1),
9
+ pageSize: z.coerce.number().default(10).describe("Results per page (default 10, max 25)"),
10
+ });
11
+ export function registerBorrowerContactsTool(server, plr) {
12
+ registerToolSafe(server, "plr_borrower_contacts", {
13
+ title: "Borrower contact information (PLR)",
14
+ description: "Get contact information (phone, email, principal names) for specific borrowers. " +
15
+ "Returns verified phone numbers, email addresses, and up to 5 associated principals/owners. " +
16
+ "Use after plr_borrower_search or plr_borrower_loans for outreach details. " +
17
+ "Accepts multiple comma-separated borrower names for batch lookups.",
18
+ inputSchema: Input,
19
+ }, async (args) => {
20
+ // API requires borrower_names as a JSON-encoded array in query param
21
+ const names = args.borrowerNames.split(",").map((s) => s.trim());
22
+ const query = {
23
+ borrower_names: JSON.stringify(names),
24
+ page: args.page,
25
+ page_size: Math.min(args.pageSize, 25),
26
+ };
27
+ if (args.state)
28
+ query.state = args.state;
29
+ const data = await plr.borrowerContacts(query);
30
+ const items = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
31
+ const lines = [
32
+ `## Borrower Contacts — ${args.borrowerNames}`,
33
+ `**Results:** ${items.length}`,
34
+ "",
35
+ ];
36
+ if (items.length > 0) {
37
+ const rows = items.slice(0, 20).map((c) => [
38
+ c.buyer_borrower1_name ?? c.borrower_name ?? "—",
39
+ c.owner_name ?? "—",
40
+ c.first_phone ?? "—",
41
+ c.has_good_phone ? "Verified" : "—",
42
+ c.first_email ?? "—",
43
+ c.most_active_msa ?? "—",
44
+ ]);
45
+ lines.push(markdownTable(["Borrower", "Owner/Principal", "Phone", "Phone Status", "Email", "Most Active MSA"], rows));
46
+ // Show social links if available
47
+ for (const c of items.slice(0, 5)) {
48
+ const socials = [];
49
+ if (c.linkedin_urls?.length)
50
+ socials.push(`LinkedIn: ${c.linkedin_urls[0]}`);
51
+ if (c.facebook_urls?.length)
52
+ socials.push(`Facebook: ${c.facebook_urls[0]}`);
53
+ if (c.instagram_urls?.length)
54
+ socials.push(`Instagram: ${c.instagram_urls[0]}`);
55
+ if (socials.length > 0) {
56
+ lines.push(`\n**${c.buyer_borrower1_name ?? "—"} socials:** ${socials.join(" | ")}`);
57
+ }
58
+ }
59
+ }
60
+ else {
61
+ lines.push("```json");
62
+ lines.push(truncatedJson(data, 3000));
63
+ lines.push("```");
64
+ }
65
+ const firstName = args.borrowerNames.split(",")[0].trim();
66
+ const actions = [
67
+ action("plr_borrower_profile", "Full borrower profile", { borrowerName: firstName }),
68
+ action("plr_borrower_loans", "Loan history", { borrowerNames: firstName }),
69
+ ];
70
+ return structuredResult(lines.join("\n"), { items: items, total: items.length, page: args.page }, actions);
71
+ });
72
+ }
73
+ //# sourceMappingURL=borrowerContacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borrowerContacts.js","sourceRoot":"","sources":["../../../src/tools/plr/borrowerContacts.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 registerBorrowerLoansTool(server: McpServer, plr: PlrClient): void;
@@ -0,0 +1,115 @@
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
+ borrowerNames: z.string().describe("Comma-separated borrower entity names — use EXACT names from search results (typically UPPERCASE, e.g. 'VAPA INVESTMENTS LLC')"),
7
+ states: z.string().optional().describe("Comma-separated state filter"),
8
+ outstandingOnly: z.boolean().optional().describe("Filter to only outstanding (active) loans. Useful for current exposure analysis."),
9
+ maturityDateBefore: z.string().optional().describe("Filter loans with maturity date before this date, e.g. '2026-12-31'"),
10
+ maturityDateAfter: z.string().optional().describe("Filter loans with maturity date after this date, e.g. '2026-01-01'"),
11
+ page: z.coerce.number().default(1).describe("Page number (default 1)"),
12
+ pageSize: z.coerce.number().default(25).describe("Results per page (default 25, max 25)"),
13
+ });
14
+ export function registerBorrowerLoansTool(server, plr) {
15
+ registerToolSafe(server, "plr_borrower_loans", {
16
+ title: "Loans for specific borrower(s) (PLR)",
17
+ description: "Get individual loan records for one or more borrowers. Returns each loan with " +
18
+ "lender, amount, property address, recording date, loan type, maturity date, and outstanding status. " +
19
+ "Use after plr_borrower_search to see which lenders a borrower uses. " +
20
+ "Filter to active loans only with outstandingOnly=true, and by maturity window with maturityDateBefore/maturityDateAfter. " +
21
+ "For contact info (phone/email), chain to plr_borrower_contacts.",
22
+ inputSchema: Input,
23
+ }, async (args) => {
24
+ const body = {
25
+ borrower_names: args.borrowerNames.split(",").map((s) => s.trim()),
26
+ page: args.page,
27
+ page_size: Math.min(args.pageSize, 25),
28
+ };
29
+ if (args.states)
30
+ body.states = args.states.split(",").map((s) => s.trim());
31
+ const columnFilters = [];
32
+ if (args.outstandingOnly) {
33
+ columnFilters.push({ type: "boolean", column: "is_loan_outstanding", operator: "is true" });
34
+ }
35
+ if (args.maturityDateBefore) {
36
+ columnFilters.push({
37
+ type: "date",
38
+ column: "first_mtg_due_date",
39
+ operator: "is before",
40
+ value: args.maturityDateBefore,
41
+ });
42
+ }
43
+ if (args.maturityDateAfter) {
44
+ columnFilters.push({
45
+ type: "date",
46
+ column: "first_mtg_due_date",
47
+ operator: "is after",
48
+ value: args.maturityDateAfter,
49
+ });
50
+ }
51
+ if (columnFilters.length > 0) {
52
+ body.column_filters = {
53
+ filters: columnFilters,
54
+ operators: Array(Math.max(0, columnFilters.length - 1)).fill("AND"),
55
+ };
56
+ }
57
+ const data = await plr.borrowerLoans(body);
58
+ const items = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
59
+ const total = data?.count ?? items.length;
60
+ const lines = [
61
+ `## Borrower Loans — ${args.borrowerNames}`,
62
+ `**Total:** ${total} | **Page:** ${args.page} | **Showing:** ${items.length}`,
63
+ "",
64
+ ];
65
+ if (items.length > 0) {
66
+ const rows = items.slice(0, 25).map((l) => [
67
+ l.full_street_address ?? l.address ?? "—",
68
+ l.city ?? "—",
69
+ l.state ?? "—",
70
+ l.lender ?? "—",
71
+ l.mtg_amt ? `$${Number(l.mtg_amt).toLocaleString()}` : "—",
72
+ (l.mtg_amt && l.sale_amt && Number(l.sale_amt) > 0) ? ((Number(l.mtg_amt) / Number(l.sale_amt)) * 100).toFixed(1) + "%" : "—",
73
+ l.recording_date ?? "—",
74
+ l.loan_type ?? "—",
75
+ ]);
76
+ lines.push(markdownTable(["Address", "City", "State", "Lender", "Amount", "LTC", "Date", "Type"], rows));
77
+ // Average LTC ratio
78
+ const loansWithLtc = items.slice(0, 25).filter((l) => l.mtg_amt && l.sale_amt && Number(l.sale_amt) > 0);
79
+ if (loansWithLtc.length > 0) {
80
+ const avgLtc = loansWithLtc.reduce((sum, l) => sum + Number(l.mtg_amt) / Number(l.sale_amt), 0) / loansWithLtc.length;
81
+ lines.push("", `**Avg LTC:** ${(avgLtc * 100).toFixed(1)}% (across ${loansWithLtc.length} loans with both amounts)`);
82
+ }
83
+ }
84
+ else {
85
+ lines.push("```json");
86
+ lines.push(truncatedJson(data, 3000));
87
+ lines.push("```");
88
+ }
89
+ // Slim items — raw API has 25+ fields, most null (resale, refi, assignment, etc.)
90
+ const slimItems = items.slice(0, 25).map((l) => ({
91
+ address: l.full_street_address ?? l.address,
92
+ city: l.city,
93
+ state: l.state,
94
+ zip: l.zip_code,
95
+ msa: l.msa,
96
+ lender: l.lender,
97
+ amount: l.mtg_amt,
98
+ recording_date: l.recording_date,
99
+ loan_type: l.loan_type,
100
+ is_outstanding: l.is_loan_outstanding ?? undefined,
101
+ maturity_date: l.first_mtg_due_date ?? undefined,
102
+ sale_amt: l.sale_amt ?? undefined,
103
+ ltc_ratio: (l.mtg_amt && l.sale_amt && Number(l.sale_amt) > 0) ? +(Number(l.mtg_amt) / Number(l.sale_amt)).toFixed(3) : undefined,
104
+ }));
105
+ const firstName = args.borrowerNames.split(",")[0].trim();
106
+ const firstAddr = items[0] ? `${items[0].full_street_address ?? items[0].address}, ${items[0].city}, ${items[0].state} ${items[0].zip_code}` : undefined;
107
+ const actions = [
108
+ action("plr_borrower_profile", "Full borrower profile", { borrowerName: firstName }),
109
+ action("plr_borrower_contacts", "Contact information", { borrowerNames: firstName }),
110
+ ...(firstAddr ? [action("sfr_get_property", "Property details for first loan", { address: firstAddr })] : []),
111
+ ];
112
+ return structuredResult(lines.join("\n"), { items: slimItems, total, page: args.page }, actions);
113
+ });
114
+ }
115
+ //# sourceMappingURL=borrowerLoans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borrowerLoans.js","sourceRoot":"","sources":["../../../src/tools/plr/borrowerLoans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;IACrB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gIAAgI,CAAC;IACpK,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;IACtE,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kFAAkF,CAAC;IACpI,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qEAAqE,CAAC;IACzH,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oEAAoE,CAAC;IACvH,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACtE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;CAC1F,CAAC,CAAC;AAEH,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,GAAc;IACzE,gBAAgB,CAAC,MAAM,EACrB,oBAAoB,EACpB;QACE,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EACT,gFAAgF;YAChF,sGAAsG;YACtG,sEAAsE;YACtE,2HAA2H;YAC3H,iEAAiE;QACnE,WAAW,EAAE,KAAK;KACnB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACX,MAAM,IAAI,GAA4B;YACpC,cAAc,EAAE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC1E,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;SACvC,CAAC;QACF,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAEnF,MAAM,aAAa,GAAmC,EAAE,CAAC;QACzD,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,aAAa,CAAC,IAAI,CAAC;gBACjB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,oBAAoB;gBAC5B,QAAQ,EAAE,WAAW;gBACrB,KAAK,EAAE,IAAI,CAAC,kBAAkB;aAC/B,CAAC,CAAC;QACL,CAAC;QACD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,aAAa,CAAC,IAAI,CAAC;gBACjB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,oBAAoB;gBAC5B,QAAQ,EAAE,UAAU;gBACpB,KAAK,EAAE,IAAI,CAAC,iBAAiB;aAC9B,CAAC,CAAC;QACL,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,cAAc,GAAG;gBACpB,OAAO,EAAE,aAAa;gBACtB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;aACpE,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,IAAI,CAAQ,CAAC;QAClD,MAAM,KAAK,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/E,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;QAE1C,MAAM,KAAK,GAAG;YACZ,uBAAuB,IAAI,CAAC,aAAa,EAAE;YAC3C,cAAc,KAAK,gBAAgB,IAAI,CAAC,IAAI,mBAAmB,KAAK,CAAC,MAAM,EAAE;YAC7E,EAAE;SACH,CAAC;QAEF,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC;gBAC9C,CAAC,CAAC,mBAAmB,IAAI,CAAC,CAAC,OAAO,IAAI,GAAG;gBACzC,CAAC,CAAC,IAAI,IAAI,GAAG;gBACb,CAAC,CAAC,KAAK,IAAI,GAAG;gBACd,CAAC,CAAC,MAAM,IAAI,GAAG;gBACf,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG;gBAC1D,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG;gBAC7H,CAAC,CAAC,cAAc,IAAI,GAAG;gBACvB,CAAC,CAAC,SAAS,IAAI,GAAG;aACnB,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,aAAa,CACtB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EACvE,IAAI,CACL,CAAC,CAAC;YAEH,oBAAoB;YACpB,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9G,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAW,EAAE,CAAM,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC;gBACnI,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,gBAAgB,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,YAAY,CAAC,MAAM,2BAA2B,CAAC,CAAC;YACvH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QAED,kFAAkF;QAClF,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,mBAAmB,IAAI,CAAC,CAAC,OAAO;YAC3C,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,GAAG,EAAE,CAAC,CAAC,QAAQ;YACf,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,MAAM,EAAE,CAAC,CAAC,OAAO;YACjB,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,cAAc,EAAE,CAAC,CAAC,mBAAmB,IAAI,SAAS;YAClD,aAAa,EAAE,CAAC,CAAC,kBAAkB,IAAI,SAAS;YAChD,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,SAAS;YACjC,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;SAClI,CAAC,CAAC,CAAC;QAEJ,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,mBAAmB,IAAI,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,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACzJ,MAAM,OAAO,GAAG;YACd,MAAM,CAAC,sBAAsB,EAAE,uBAAuB,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;YACpF,MAAM,CAAC,uBAAuB,EAAE,qBAAqB,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;YACpF,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,iCAAiC,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9G,CAAC;QAEF,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACrG,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -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 registerBorrowerProfileTool(server: McpServer, plr: PlrClient): void;
@@ -0,0 +1,201 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, kvSummary, fmtDollars } from "../formatters.js";
3
+ import { registerToolSafe } from "../registerToolSafe.js";
4
+ import { action } from "../nextActions.js";
5
+ const Input = z.object({
6
+ borrowerName: z.string().describe("Borrower entity name — use EXACT name from search results (typically UPPERCASE, e.g. 'VAPA INVESTMENTS LLC')"),
7
+ state: z.string().optional().describe("Optional state filter, e.g. 'CA'"),
8
+ });
9
+ export function registerBorrowerProfileTool(server, plr) {
10
+ registerToolSafe(server, "plr_borrower_profile", {
11
+ title: "Comprehensive borrower profile — loans, lenders & contacts (PLR)",
12
+ description: "Get a comprehensive profile of a private lending borrower by name. " +
13
+ "Returns loan history, lender relationships, geographic activity, contact info, " +
14
+ "and loan type breakdowns (bridge/DSCR). " +
15
+ "Use plr_borrower_search first to discover borrower names." +
16
+ " See plr_borrower_loans for detailed loan list and plr_borrower_contacts for contact information.",
17
+ inputSchema: Input,
18
+ }, async (args) => {
19
+ const query = {
20
+ borrower_name: args.borrowerName,
21
+ };
22
+ if (args.state)
23
+ query.state = args.state;
24
+ const data = await plr.borrowerProfile(query);
25
+ // If the profile API succeeds, format normally
26
+ if (!data?.error) {
27
+ return formatProfileResponse(args, data);
28
+ }
29
+ // Profile API has a known backend bug — fall back to loans + contacts
30
+ return compositeFallback(plr, args);
31
+ });
32
+ }
33
+ /** Format a successful profile API response */
34
+ function formatProfileResponse(args, data) {
35
+ const items = data?.results ?? [];
36
+ const total = data?.count ?? items.length;
37
+ const lines = [`## Borrower Profile — ${args.borrowerName}`, ""];
38
+ lines.push(`**Total Loan Records:** ${total}`);
39
+ lines.push(`**Borrower:** ${data?.borrower ?? args.borrowerName}`);
40
+ lines.push("");
41
+ if (items.length > 0) {
42
+ const fmt$ = (v) => v != null ? `$${Number(v).toLocaleString()}` : "—";
43
+ const rows = items.slice(0, 15).map((r) => [
44
+ r.recording_date ?? r.max_recording_date ?? "—",
45
+ r.lender ?? "—",
46
+ fmt$(r.mortgage_amt),
47
+ r.num_properties ?? "—",
48
+ r.first_mtg_term ? `${r.first_mtg_term}mo` : "—",
49
+ ]);
50
+ lines.push(markdownTable(["Date", "Lender", "Amount", "Properties", "Term"], rows));
51
+ }
52
+ else {
53
+ lines.push("_No loan records found for this borrower._");
54
+ }
55
+ const actions = [
56
+ action("plr_borrower_loans", "Full loan history", { borrowerNames: args.borrowerName }),
57
+ action("plr_borrower_contacts", "Contact information", { borrowerNames: args.borrowerName }),
58
+ ];
59
+ const structured = {
60
+ borrower: data?.borrower ?? args.borrowerName,
61
+ count: total,
62
+ results: items.slice(0, 15).map((r) => ({
63
+ recording_date: r.recording_date ?? r.max_recording_date,
64
+ lender: r.lender,
65
+ mortgage_amt: r.mortgage_amt,
66
+ num_properties: r.num_properties,
67
+ first_mtg_term: r.first_mtg_term,
68
+ })),
69
+ };
70
+ return structuredResult(lines.join("\n"), structured, actions);
71
+ }
72
+ /** Build a composite profile from borrowerLoans + borrowerContacts */
73
+ async function compositeFallback(plr, args) {
74
+ const name = args.borrowerName;
75
+ const loansBody = {
76
+ borrower_names: [name],
77
+ page: 1,
78
+ page_size: 100,
79
+ };
80
+ if (args.state)
81
+ loansBody.states = [args.state];
82
+ // Fetch loans and contacts in parallel
83
+ const [loansData, contactsData] = await Promise.all([
84
+ plr.borrowerLoans(loansBody),
85
+ plr.borrowerContacts({
86
+ borrower_names: JSON.stringify([name]),
87
+ }),
88
+ ]);
89
+ const loans = loansData?.results ?? [];
90
+ const totalLoans = loansData?.count ?? loans.length;
91
+ const contact = (contactsData?.results ?? [])[0];
92
+ if (totalLoans === 0 && !contact) {
93
+ const lines = [
94
+ `## Borrower Profile — ${name}`,
95
+ "",
96
+ "_No data found for this borrower. Check spelling or use plr_borrower_search to find similar names._",
97
+ ];
98
+ return structuredResult(lines.join("\n"), { borrower: name, error: "not_found" });
99
+ }
100
+ // Aggregate stats from loan records
101
+ const lenderSet = new Set();
102
+ const stateSet = new Set();
103
+ const msaSet = new Set();
104
+ let bridgeCount = 0;
105
+ let dscrCount = 0;
106
+ let totalVolume = 0;
107
+ for (const l of loans) {
108
+ if (l.lender)
109
+ lenderSet.add(l.lender);
110
+ if (l.state)
111
+ stateSet.add(l.state);
112
+ if (l.msa)
113
+ msaSet.add(l.msa);
114
+ if (l.loan_type === "Bridge")
115
+ bridgeCount++;
116
+ if (l.loan_type === "DSCR")
117
+ dscrCount++;
118
+ totalVolume += Number(l.mtg_amt) || 0;
119
+ }
120
+ const lenders = [...lenderSet];
121
+ const states = [...stateSet].sort();
122
+ const msas = [...msaSet].sort();
123
+ const lines = [`## Borrower Profile — ${name}`, ""];
124
+ lines.push(kvSummary([
125
+ ["Total Loans", totalLoans],
126
+ ["Bridge Loans", bridgeCount || undefined],
127
+ ["DSCR Loans", dscrCount || undefined],
128
+ ["Total Volume (sampled)", totalVolume > 0 ? fmtDollars(totalVolume) : undefined],
129
+ ["Lenders Used", lenders.length > 0 ? `${lenders.length} (${lenders.slice(0, 5).join(", ")}${lenders.length > 5 ? "..." : ""})` : undefined],
130
+ ["States", states.join(", ") || undefined],
131
+ ]));
132
+ lines.push("");
133
+ // Contact info
134
+ if (contact) {
135
+ const contactPairs = [];
136
+ if (contact.owner_name)
137
+ contactPairs.push(["Owner", contact.owner_name]);
138
+ if (contact.first_phone)
139
+ contactPairs.push(["Phone", `${contact.first_phone}${contact.has_good_phone ? " (verified)" : ""}`]);
140
+ if (contact.second_phone)
141
+ contactPairs.push(["Phone 2", contact.second_phone]);
142
+ if (contact.first_email)
143
+ contactPairs.push(["Email", contact.first_email]);
144
+ if (contact.most_active_msa)
145
+ contactPairs.push(["Primary MSA", contact.most_active_msa]);
146
+ if (contactPairs.length > 0) {
147
+ lines.push("### Contact");
148
+ lines.push(kvSummary(contactPairs));
149
+ lines.push("");
150
+ }
151
+ }
152
+ // Recent loans table
153
+ if (loans.length > 0) {
154
+ lines.push("### Recent Loans");
155
+ const fmt$ = (v) => v != null ? `$${Number(v).toLocaleString()}` : "—";
156
+ const rows = loans.slice(0, 10).map((l) => [
157
+ l.recording_date ?? "—",
158
+ l.lender ?? "—",
159
+ fmt$(l.mtg_amt),
160
+ l.loan_type ?? "—",
161
+ [l.city, l.state].filter(Boolean).join(", ") || "—",
162
+ ]);
163
+ lines.push(markdownTable(["Date", "Lender", "Amount", "Type", "Location"], rows));
164
+ }
165
+ if (msas.length > 0) {
166
+ lines.push(`**MSAs:** ${msas.slice(0, 10).join(", ")}${msas.length > 10 ? "..." : ""}`);
167
+ }
168
+ const structured = {
169
+ borrower_name: name,
170
+ total_loans: totalLoans,
171
+ bridge_loans: bridgeCount,
172
+ dscr_loans: dscrCount,
173
+ total_volume_sampled: totalVolume,
174
+ lenders,
175
+ states,
176
+ msas: msas.slice(0, 20),
177
+ contact: contact ? {
178
+ owner: contact.owner_name,
179
+ phone: contact.first_phone,
180
+ verified_phone: contact.has_good_phone,
181
+ email: contact.first_email,
182
+ primary_msa: contact.most_active_msa,
183
+ } : null,
184
+ recent_loans: loans.slice(0, 10).map((l) => ({
185
+ date: l.recording_date,
186
+ lender: l.lender,
187
+ amount: l.mtg_amt,
188
+ type: l.loan_type,
189
+ city: l.city,
190
+ state: l.state,
191
+ })),
192
+ source: "composite_fallback",
193
+ };
194
+ const actions = [
195
+ action("plr_borrower_loans", "Full loan history", { borrowerNames: name }),
196
+ action("plr_borrower_contacts", "Contact information", { borrowerNames: name }),
197
+ ...(lenders.length > 0 ? [action("plr_lender_borrowers", "Other borrowers of same lender", { lenders: lenders[0] })] : []),
198
+ ];
199
+ return structuredResult(lines.join("\n"), structured, actions);
200
+ }
201
+ //# sourceMappingURL=borrowerProfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borrowerProfile.js","sourceRoot":"","sources":["../../../src/tools/plr/borrowerProfile.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 registerBorrowerRankingsTool(server: McpServer, plr: PlrClient): void;
@@ -0,0 +1,123 @@
1
+ import { z } from "zod";
2
+ import { structuredResult, markdownTable, 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 borrower name"),
8
+ states: z.string().optional().describe("Filter by entity registration state (where LLC is registered). Comma-separated, e.g. 'CA,TX'. Use MSA filter for geographic activity."),
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 registerBorrowerRankingsTool(server, plr) {
17
+ registerToolSafe(server, "plr_borrower_rankings", {
18
+ title: "Rank borrowers with period-over-period growth (PLR)",
19
+ description: "Rank borrowers by loan count and volume, showing GROWTH vs. the prior period. " +
20
+ "Filter by geography (state/MSA), loan type, and date range. " +
21
+ "Best for 'which borrowers are growing/declining?' — use when growth trends matter. " +
22
+ "For a simple borrower list by activity (without growth metrics), use plr_borrower_search. " +
23
+ "Different from plr_top_borrowers which is nationwide-only. " +
24
+ "CHAIN: Pass borrower names to sfr_distress_search (as owner_name + state) to find distressed properties they own.",
25
+ inputSchema: Input,
26
+ }, async (args) => {
27
+ const body = {
28
+ page: args.page,
29
+ page_size: Math.min(args.pageSize, 25),
30
+ };
31
+ if (args.search)
32
+ body.search = args.search;
33
+ if (args.states)
34
+ body.states = args.states.split(",").map((s) => s.trim());
35
+ if (args.msas)
36
+ body.msas = args.msas.split(";").map((s) => s.trim());
37
+ if (args.startDate)
38
+ body.start_date = args.startDate;
39
+ if (args.endDate)
40
+ body.end_date = args.endDate;
41
+ if (args.loanTypes)
42
+ body.loan_types = args.loanTypes.split(",").map((s) => s.trim());
43
+ autoFillPlrDates(body);
44
+ const data = await plr.borrowerRankings(body);
45
+ const rawItems = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
46
+ // Deduplicate: API returns one row per owner, causing duplicate borrower entries.
47
+ // Group by borrower name, merge owner names, keep highest loan count row.
48
+ const seen = new Map();
49
+ for (const item of rawItems) {
50
+ const name = item.borrower ?? "";
51
+ const existing = seen.get(name);
52
+ if (!existing) {
53
+ seen.set(name, { ...item, _owners: item.owner_name ? [item.owner_name] : [] });
54
+ }
55
+ else {
56
+ // Merge owner name
57
+ if (item.owner_name && !existing._owners.includes(item.owner_name)) {
58
+ existing._owners.push(item.owner_name);
59
+ }
60
+ // Keep the row with more loans
61
+ if ((item.num_loans ?? 0) > (existing.num_loans ?? 0)) {
62
+ const owners = existing._owners;
63
+ Object.assign(existing, item);
64
+ existing._owners = owners;
65
+ }
66
+ }
67
+ }
68
+ const items = [...seen.values()];
69
+ const total = data?.count ?? rawItems.length;
70
+ const deduped = rawItems.length - items.length;
71
+ const lines = [
72
+ `## Borrower Rankings`,
73
+ `**Total:** ${total} | **Page:** ${args.page} | **Showing:** ${items.length}${deduped > 0 ? ` (${deduped} duplicate owner rows merged)` : ""}`,
74
+ "",
75
+ ];
76
+ if (items.length > 0) {
77
+ const rows = items.slice(0, 25).map((b, i) => [
78
+ i + 1 + (args.page - 1) * args.pageSize,
79
+ b.borrower ?? "—",
80
+ b.num_loans ?? "—",
81
+ fmtDollars(b.mortgage_amt),
82
+ b.num_properties ?? "—",
83
+ b.last_loan_type ?? "—",
84
+ Array.isArray(b.states) ? [...new Set(b.states)].join(", ") : "—",
85
+ ]);
86
+ lines.push(markdownTable(["#", "Borrower", "Loans", "Volume", "Properties", "Type", "States"], rows));
87
+ }
88
+ else {
89
+ lines.push("_No borrowers found._ " +
90
+ "Try widening the date range, removing the loanTypes filter, or checking MSA name spelling. " +
91
+ "Use plr_msa_rankings to discover correct PLR MSA names.");
92
+ }
93
+ // Slim items for structured output — cap large arrays (addresses, cities, zips)
94
+ const capArray = (arr, max = 5) => {
95
+ if (!Array.isArray(arr))
96
+ return arr;
97
+ const unique = [...new Set(arr)];
98
+ if (unique.length <= max)
99
+ return unique;
100
+ return [...unique.slice(0, max), `...and ${unique.length - max} more`];
101
+ };
102
+ const slimItems = items.slice(0, 25).map((b) => ({
103
+ borrower: b.borrower,
104
+ num_loans: b.num_loans,
105
+ mortgage_amt: b.mortgage_amt,
106
+ num_properties: b.num_properties,
107
+ last_loan_type: b.last_loan_type,
108
+ states: capArray(b.states),
109
+ msas: capArray(b.msas),
110
+ addresses: capArray(b.addresses, 3),
111
+ cities: capArray(b.cities),
112
+ owners: b._owners?.length > 0 ? b._owners : undefined,
113
+ }));
114
+ const firstName = items[0]?.borrower;
115
+ const actions = [
116
+ ...(firstName ? [action("plr_borrower_profile", "Full profile for top borrower", { borrowerName: firstName })] : []),
117
+ ...(firstName ? [action("plr_borrower_loans", "Loan history for top borrower", { borrowerNames: firstName })] : []),
118
+ ...(firstName ? [action("plr_borrower_contacts", "Contact info for top borrower", { borrowerNames: firstName })] : []),
119
+ ];
120
+ return structuredResult(lines.join("\n"), { items: slimItems, total, page: args.page }, actions);
121
+ });
122
+ }
123
+ //# sourceMappingURL=borrowerRankings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borrowerRankings.js","sourceRoot":"","sources":["../../../src/tools/plr/borrowerRankings.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 registerBorrowerSearchTool(server: McpServer, plr: PlrClient): void;