@sfranalytics/mcp 0.6.3 → 0.6.5
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.
- package/dist/http.js +69 -3
- package/dist/http.js.map +1 -1
- package/dist/landing.js +17 -4
- package/dist/landing.js.map +1 -1
- package/dist/server.js +5 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/formatters.js +6 -1
- package/dist/tools/formatters.js.map +1 -1
- package/dist/tools/plr/borrowerProfile.js +5 -2
- package/dist/tools/plr/borrowerProfile.js.map +1 -1
- package/dist/tools/plr/borrowerSearch.js +1 -1
- package/dist/tools/plr/borrowerSearch.js.map +1 -1
- package/dist/tools/plr/loansNearby.js +100 -84
- package/dist/tools/plr/loansNearby.js.map +1 -1
- package/dist/tools/plr/transactionHistory.js +67 -50
- package/dist/tools/plr/transactionHistory.js.map +1 -1
- package/dist/tools/sfr/bestBuyers.js +58 -41
- package/dist/tools/sfr/bestBuyers.js.map +1 -1
- package/dist/tools/sfr/buyerProfile.js +12 -5
- package/dist/tools/sfr/buyerProfile.js.map +1 -1
- package/dist/tools/sfr/getProperty.js +147 -124
- package/dist/tools/sfr/getProperty.js.map +1 -1
- package/dist/tools/sfr/propertyBatch.js +3 -1
- package/dist/tools/sfr/propertyBatch.js.map +1 -1
- package/dist/tools/sfr/propertyComps.js +51 -34
- package/dist/tools/sfr/propertyComps.js.map +1 -1
- package/dist/tools/sfr/propertyTransactions.js +48 -31
- package/dist/tools/sfr/propertyTransactions.js.map +1 -1
- package/dist/tools/sfr/rentalComparables.js +83 -66
- package/dist/tools/sfr/rentalComparables.js.map +1 -1
- package/dist/tools/sfr/rentalMarketAnalysis.js +5 -2
- package/dist/tools/sfr/rentalMarketAnalysis.js.map +1 -1
- package/dist/tools/sfr/searchProperties.js +3 -1
- package/dist/tools/sfr/searchProperties.js.map +1 -1
- package/dist/tools/sfr/topBuyers.js +13 -10
- package/dist/tools/sfr/topBuyers.js.map +1 -1
- package/dist/tools/sfr/zipDetail.js +83 -72
- package/dist/tools/sfr/zipDetail.js.map +1 -1
- package/dist/tools/sfr/zipFinder.js +24 -15
- package/dist/tools/sfr/zipFinder.js.map +1 -1
- package/dist/tools/support.d.ts +3 -0
- package/dist/tools/support.js +60 -0
- package/dist/tools/support.js.map +1 -0
- package/package.json +1 -1
|
@@ -3,10 +3,13 @@ import { structuredResult, markdownTable } from "../formatters.js";
|
|
|
3
3
|
import { registerToolSafe } from "../registerToolSafe.js";
|
|
4
4
|
import { autoFillPlrDates } from "../dateHelper.js";
|
|
5
5
|
import { action } from "../nextActions.js";
|
|
6
|
+
import { ApiError } from "../../services/httpClient.js";
|
|
6
7
|
const Input = z.object({
|
|
7
8
|
propertyAddress: z
|
|
8
9
|
.string()
|
|
9
|
-
.describe("Full property address
|
|
10
|
+
.describe("Full property address (e.g. '123 Main St, Phoenix, AZ 85004'). " +
|
|
11
|
+
"Used for geocoding — must be close enough to resolve. " +
|
|
12
|
+
"If geocoding fails, try a nearby address or use plr_borrower_search with geographic filters."),
|
|
10
13
|
radius: z.coerce.number().default(1).describe("Search radius in miles (default 1)"),
|
|
11
14
|
limit: z.coerce.number().default(25).describe("Maximum results (default 25)"),
|
|
12
15
|
startDate: z.string().optional().describe("Start date, e.g. '2023-01-01'"),
|
|
@@ -22,92 +25,105 @@ export function registerLoansNearbyTool(server, plr) {
|
|
|
22
25
|
"For a specific property's lending history, use plr_transaction_history.",
|
|
23
26
|
inputSchema: Input,
|
|
24
27
|
}, async (args) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
28
|
+
try {
|
|
29
|
+
const query = {
|
|
30
|
+
property_address: args.propertyAddress,
|
|
31
|
+
radius: args.radius,
|
|
32
|
+
limit: args.limit,
|
|
33
|
+
};
|
|
34
|
+
if (args.startDate)
|
|
35
|
+
query.start_date = args.startDate;
|
|
36
|
+
if (args.endDate)
|
|
37
|
+
query.end_date = args.endDate;
|
|
38
|
+
autoFillPlrDates(query);
|
|
39
|
+
const data = await plr.loansNearby(query);
|
|
40
|
+
const rawItems = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
|
|
41
|
+
// Deduplicate: API returns duplicate rows (same lender+borrower+address+amount+date)
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const items = rawItems.filter((l) => {
|
|
44
|
+
const key = `${l.lender}|${l.buyer_borrower1_name}|${l.full_street_address}|${l.mtg_amt ?? l.loan_amount}|${l.recording_date}`;
|
|
45
|
+
if (seen.has(key))
|
|
46
|
+
return false;
|
|
47
|
+
seen.add(key);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
const lines = [
|
|
51
|
+
`## Loans Nearby — ${args.propertyAddress}`,
|
|
52
|
+
`**Radius:** ${args.radius} mi | **Found:** ${items.length}`,
|
|
53
|
+
"",
|
|
54
|
+
];
|
|
55
|
+
// Distinguish "address not geocoded" from "no loans in area"
|
|
56
|
+
if (items.length === 0 && data?.property_details === null) {
|
|
57
|
+
lines.push("_Could not geocode this address — the property was not found in the SFR database. " +
|
|
58
|
+
"Try a nearby address or use plr_borrower_search with geographic filters instead._");
|
|
59
|
+
return structuredResult(lines.join("\n"), { items, total: items.length, page: 1 });
|
|
60
|
+
}
|
|
61
|
+
if (items.length > 0) {
|
|
62
|
+
const fmt$ = (v) => v != null && Number(v) > 0 ? `$${Number(v).toLocaleString()}` : "—";
|
|
63
|
+
const rows = items.slice(0, 25).map((l) => [
|
|
64
|
+
l.full_street_address ?? l.address ?? "—",
|
|
65
|
+
`${l.city ?? ""}, ${l.state ?? ""}`.replace(/^, |, $/, "") || "—",
|
|
66
|
+
l.lender ?? "—",
|
|
67
|
+
l.buyer_borrower1_name ?? l.borrower ?? "—",
|
|
68
|
+
fmt$(l.loan_amount ?? l.mtg_amt),
|
|
69
|
+
l.recording_date ?? "—",
|
|
70
|
+
]);
|
|
71
|
+
lines.push(markdownTable(["Address", "Location", "Lender", "Borrower", "Amount", "Date"], rows));
|
|
72
|
+
// Show subject property details if available at top level
|
|
73
|
+
if (data?.property_details) {
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push("### Subject Property Details");
|
|
76
|
+
const pd = data.property_details;
|
|
77
|
+
const detailLines = [
|
|
78
|
+
pd.avm_value ? `**AVM:** $${Number(pd.avm_value).toLocaleString()}` : null,
|
|
79
|
+
pd.market_value ? `**Market Value:** $${Number(pd.market_value).toLocaleString()}` : null,
|
|
80
|
+
pd.owner ? `**Owner:** ${pd.owner}` : null,
|
|
81
|
+
pd.owner_occupied != null ? `**Owner Occupied:** ${pd.owner_occupied}` : null,
|
|
82
|
+
pd.pfc_flag ? `**Pre-Foreclosure:** Yes (${pd.pfc_recording_date ?? ""})` : null,
|
|
83
|
+
pd.vacant_flag ? `**Vacant:** Yes` : null,
|
|
84
|
+
].filter(Boolean);
|
|
85
|
+
lines.push(detailLines.join(" | "));
|
|
86
|
+
}
|
|
82
87
|
}
|
|
88
|
+
else {
|
|
89
|
+
lines.push("_No loans found within radius._");
|
|
90
|
+
}
|
|
91
|
+
// Slim items for structured output
|
|
92
|
+
const slimItems = items.slice(0, 25).map((l) => ({
|
|
93
|
+
address: l.full_street_address,
|
|
94
|
+
city: l.city,
|
|
95
|
+
state: l.state,
|
|
96
|
+
zip: l.zip_code,
|
|
97
|
+
lender: l.lender,
|
|
98
|
+
borrower: l.buyer_borrower1_name,
|
|
99
|
+
amount: l.mtg_amt ?? l.loan_amount,
|
|
100
|
+
recording_date: l.recording_date,
|
|
101
|
+
loan_type: l.loan_type ?? l.land_use_category,
|
|
102
|
+
}));
|
|
103
|
+
const firstBorrower = items[0]?.buyer_borrower1_name ?? items[0]?.borrower;
|
|
104
|
+
const actions = [
|
|
105
|
+
...(firstBorrower ? [action("plr_borrower_profile", "Profile nearest borrower", { borrowerName: firstBorrower })] : []),
|
|
106
|
+
action("sfr_get_property", "Full property details", { address: args.propertyAddress }),
|
|
107
|
+
action("plr_transaction_history", "Property lending history", { propertyAddress: args.propertyAddress }),
|
|
108
|
+
];
|
|
109
|
+
return structuredResult(lines.join("\n"), {
|
|
110
|
+
items: slimItems,
|
|
111
|
+
total: items.length,
|
|
112
|
+
deduped: rawItems.length - items.length > 0 ? rawItems.length - items.length : undefined,
|
|
113
|
+
page: 1,
|
|
114
|
+
}, actions);
|
|
83
115
|
}
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
118
|
+
const zipMatch = args.propertyAddress.match(/\b(\d{5})\b/);
|
|
119
|
+
const zip = zipMatch?.[1];
|
|
120
|
+
return structuredResult(`## Loans Nearby — ${args.propertyAddress}\n\n` +
|
|
121
|
+
`_Address not found or could not be geocoded. Verify the exact address format. ` +
|
|
122
|
+
`Use sfr_search_properties with the zip code to find the canonical address, ` +
|
|
123
|
+
`or use plr_borrower_search with geographic filters instead._`, { address: args.propertyAddress, error: "not_found" }, [action("sfr_search_properties", "Search by zip to find correct address", zip ? { search_type: "zip", zips: zip } : undefined)]);
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
86
126
|
}
|
|
87
|
-
// Slim items for structured output
|
|
88
|
-
const slimItems = items.slice(0, 25).map((l) => ({
|
|
89
|
-
address: l.full_street_address,
|
|
90
|
-
city: l.city,
|
|
91
|
-
state: l.state,
|
|
92
|
-
zip: l.zip_code,
|
|
93
|
-
lender: l.lender,
|
|
94
|
-
borrower: l.buyer_borrower1_name,
|
|
95
|
-
amount: l.mtg_amt ?? l.loan_amount,
|
|
96
|
-
recording_date: l.recording_date,
|
|
97
|
-
loan_type: l.loan_type ?? l.land_use_category,
|
|
98
|
-
}));
|
|
99
|
-
const firstBorrower = items[0]?.buyer_borrower1_name ?? items[0]?.borrower;
|
|
100
|
-
const actions = [
|
|
101
|
-
...(firstBorrower ? [action("plr_borrower_profile", "Profile nearest borrower", { borrowerName: firstBorrower })] : []),
|
|
102
|
-
action("sfr_get_property", "Full property details", { address: args.propertyAddress }),
|
|
103
|
-
action("plr_transaction_history", "Property lending history", { propertyAddress: args.propertyAddress }),
|
|
104
|
-
];
|
|
105
|
-
return structuredResult(lines.join("\n"), {
|
|
106
|
-
items: slimItems,
|
|
107
|
-
total: items.length,
|
|
108
|
-
deduped: rawItems.length - items.length > 0 ? rawItems.length - items.length : undefined,
|
|
109
|
-
page: 1,
|
|
110
|
-
}, actions);
|
|
111
127
|
});
|
|
112
128
|
}
|
|
113
129
|
//# sourceMappingURL=loansNearby.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loansNearby.js","sourceRoot":"","sources":["../../../src/tools/plr/loansNearby.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"loansNearby.js","sourceRoot":"","sources":["../../../src/tools/plr/loansNearby.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
|
|
3
3
|
import { registerToolSafe } from "../registerToolSafe.js";
|
|
4
4
|
import { action } from "../nextActions.js";
|
|
5
|
+
import { ApiError } from "../../services/httpClient.js";
|
|
5
6
|
const Input = z.object({
|
|
6
|
-
propertyAddress: z.string().describe("Full property address
|
|
7
|
+
propertyAddress: z.string().describe("Full property address exactly as recorded (e.g. '123 Main St, Phoenix, AZ 85004'). " +
|
|
8
|
+
"Must match dataset precisely — abbreviations (St/Ave/Blvd), directional prefixes (N/S/E/W), " +
|
|
9
|
+
"and unit numbers matter. If unsure, use sfr_search_properties with the zip code first to find " +
|
|
10
|
+
"the canonical address."),
|
|
7
11
|
page: z.coerce.number().default(1).describe("Page number (default 1)"),
|
|
8
12
|
pageSize: z.coerce.number().default(10).describe("Results per page (default 10, max 25)"),
|
|
9
13
|
});
|
|
@@ -16,59 +20,72 @@ export function registerTransactionHistoryTool(server, plr) {
|
|
|
16
20
|
"Complements sfr_property_transactions which shows deeds/sales.",
|
|
17
21
|
inputSchema: Input,
|
|
18
22
|
}, async (args) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
23
|
+
try {
|
|
24
|
+
const body = {
|
|
25
|
+
property_address: args.propertyAddress,
|
|
26
|
+
page: args.page,
|
|
27
|
+
page_size: Math.min(args.pageSize, 25),
|
|
28
|
+
};
|
|
29
|
+
const data = await plr.transactionHistory(body);
|
|
30
|
+
const items = data?.results ?? data?.data ?? (Array.isArray(data) ? data : []);
|
|
31
|
+
const total = data?.count ?? items.length;
|
|
32
|
+
const lines = [
|
|
33
|
+
`## Private Lending History — ${args.propertyAddress}`,
|
|
34
|
+
`**Total:** ${total} transactions`,
|
|
35
|
+
"",
|
|
36
|
+
];
|
|
37
|
+
if (items.length > 0) {
|
|
38
|
+
const fmt$ = (v) => v != null && Number(v) > 0 ? `$${Number(v).toLocaleString()}` : "—";
|
|
39
|
+
const rows = items.slice(0, 20).map((t) => [
|
|
40
|
+
t.recording_date ?? "—",
|
|
41
|
+
t.tx_type ?? t.transaction_type_clean ?? "—",
|
|
42
|
+
fmt$(t.sale_amt),
|
|
43
|
+
t.buyer_borrower1_name ?? "—",
|
|
44
|
+
t.seller1_name ?? "—",
|
|
45
|
+
t.lender_clean ?? t.first_mtg_lender_name ?? "—",
|
|
46
|
+
fmt$(t.first_mtg_amt),
|
|
47
|
+
]);
|
|
48
|
+
lines.push(markdownTable(["Date", "Type", "Sale Price", "Buyer", "Seller", "Lender", "Mortgage"], rows));
|
|
49
|
+
if (data.property_details) {
|
|
50
|
+
const pd = data.property_details;
|
|
51
|
+
lines.push("", "### Property Details");
|
|
52
|
+
const detailLines = [
|
|
53
|
+
pd.avm_value ? `**AVM:** $${Number(pd.avm_value).toLocaleString()}` : null,
|
|
54
|
+
pd.market_value ? `**Market Value:** $${Number(pd.market_value).toLocaleString()}` : null,
|
|
55
|
+
pd.owner ? `**Owner:** ${pd.owner}` : null,
|
|
56
|
+
pd.property_type ? `**Type:** ${pd.property_type}` : null,
|
|
57
|
+
pd.beds ? `**Beds:** ${pd.beds}` : null,
|
|
58
|
+
pd.total_sqft ? `**Sqft:** ${pd.total_sqft}` : null,
|
|
59
|
+
].filter(Boolean);
|
|
60
|
+
lines.push(detailLines.join(" | "));
|
|
61
|
+
}
|
|
56
62
|
}
|
|
63
|
+
else {
|
|
64
|
+
lines.push("_No private lending transactions found for this property._");
|
|
65
|
+
if (data && typeof data === "object") {
|
|
66
|
+
lines.push("```json");
|
|
67
|
+
lines.push(truncatedJson(data, 2000));
|
|
68
|
+
lines.push("```");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const actions = [
|
|
72
|
+
action("sfr_get_property", "Full property details & valuation", { address: args.propertyAddress }),
|
|
73
|
+
action("plr_loans_nearby", "Other loans in the area", { propertyAddress: args.propertyAddress }),
|
|
74
|
+
action("sfr_property_transactions", "Deed & sale history", { address: args.propertyAddress }),
|
|
75
|
+
];
|
|
76
|
+
return structuredResult(lines.join("\n"), { items, total, page: args.page }, actions);
|
|
57
77
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
80
|
+
const zipMatch = args.propertyAddress.match(/\b(\d{5})\b/);
|
|
81
|
+
const zip = zipMatch?.[1];
|
|
82
|
+
return structuredResult(`## Private Lending History — ${args.propertyAddress}\n\n` +
|
|
83
|
+
`_Property not found at this address. The address must exactly match the dataset ` +
|
|
84
|
+
`(including directional prefixes like 'E'/'W', abbreviations like 'St'/'Ave', and unit numbers). ` +
|
|
85
|
+
`Use sfr_search_properties with the zip code to find the canonical address._`, { address: args.propertyAddress, error: "not_found" }, [action("sfr_search_properties", "Search by zip to find correct address", zip ? { search_type: "zip", zips: zip } : undefined)]);
|
|
64
86
|
}
|
|
87
|
+
throw err;
|
|
65
88
|
}
|
|
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
89
|
});
|
|
73
90
|
}
|
|
74
91
|
//# sourceMappingURL=transactionHistory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transactionHistory.js","sourceRoot":"","sources":["../../../src/tools/plr/transactionHistory.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;
|
|
1
|
+
{"version":3,"file":"transactionHistory.js","sourceRoot":"","sources":["../../../src/tools/plr/transactionHistory.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
import { structuredResult, markdownTable, truncatedJson } from "../formatters.js";
|
|
3
3
|
import { action } from "../nextActions.js";
|
|
4
4
|
import { registerToolSafe } from "../registerToolSafe.js";
|
|
5
|
+
import { ApiError } from "../../services/httpClient.js";
|
|
5
6
|
const Input = z.object({
|
|
6
|
-
address: z.string().describe("Full property address
|
|
7
|
+
address: z.string().describe("Full property address exactly as recorded (e.g. '123 Main St, Phoenix, AZ 85004'). " +
|
|
8
|
+
"Must match dataset precisely — abbreviations (St/Ave/Blvd), directional prefixes (N/S/E/W), " +
|
|
9
|
+
"and unit numbers matter. If unsure, use sfr_search_properties with the zip code first to find " +
|
|
10
|
+
"the canonical address."),
|
|
7
11
|
});
|
|
8
12
|
export function registerBestBuyersTool(server, sfr) {
|
|
9
13
|
registerToolSafe(server, "sfr_best_buyers", {
|
|
@@ -13,48 +17,61 @@ export function registerBestBuyersTool(server, sfr) {
|
|
|
13
17
|
"Ideal for sellers, wholesalers, or agents matching properties to active buyers.",
|
|
14
18
|
inputSchema: Input,
|
|
15
19
|
}, async (args) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
i
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
try {
|
|
21
|
+
const data = await sfr.getBestBuyers(args.address);
|
|
22
|
+
const buyers = data?.data ?? data?.buyers ?? (Array.isArray(data) ? data : []);
|
|
23
|
+
const lines = [
|
|
24
|
+
`## Best Buyers — ${args.address}`,
|
|
25
|
+
`**Matches:** ${buyers.length} investors`,
|
|
26
|
+
"",
|
|
27
|
+
];
|
|
28
|
+
if (buyers.length > 0) {
|
|
29
|
+
// API returns {name, matchScore, totalAcquisitions, recentPurchasesCount, matchReasons}
|
|
30
|
+
const rows = buyers.slice(0, 25).map((b, i) => [
|
|
31
|
+
i + 1,
|
|
32
|
+
b.name ?? b.buyer_name ?? "—",
|
|
33
|
+
b.totalAcquisitions ?? b.acquisitions ?? "—",
|
|
34
|
+
b.recentPurchasesCount ?? "—",
|
|
35
|
+
b.matchScore != null ? `${b.matchScore}%` : "—",
|
|
36
|
+
Array.isArray(b.matchReasons) ? b.matchReasons[0] ?? "—" : "—",
|
|
37
|
+
]);
|
|
38
|
+
lines.push(markdownTable(["#", "Buyer", "Total Acq.", "Recent", "Match", "Reason"], rows));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines.push("```json");
|
|
42
|
+
lines.push(truncatedJson(data, 3000));
|
|
43
|
+
lines.push("```");
|
|
44
|
+
}
|
|
45
|
+
// Slim buyers — keep match-specific fields, strip URL slugs
|
|
46
|
+
const slimBuyers = buyers.slice(0, 25).map((b) => ({
|
|
47
|
+
name: b.name ?? b.buyer_name,
|
|
48
|
+
totalAcquisitions: b.totalAcquisitions ?? b.acquisitions,
|
|
49
|
+
recentPurchasesCount: b.recentPurchasesCount,
|
|
50
|
+
purchasesWithinQuarterMile: b.purchasesWithinQuarterMile || undefined,
|
|
51
|
+
matchScore: b.matchScore,
|
|
52
|
+
matchReasons: b.matchReasons,
|
|
53
|
+
hasProfile: b.hasProfilePage || undefined,
|
|
54
|
+
hasContact: b.hasContactInfo || undefined,
|
|
55
|
+
}));
|
|
56
|
+
const firstName = buyers[0]?.name ?? buyers[0]?.buyer_name;
|
|
57
|
+
const actions = [
|
|
58
|
+
...(firstName ? [action("sfr_buyer_profile", "Profile top match", { name: firstName })] : []),
|
|
59
|
+
...(firstName ? [action("sfr_buyer_growth", "Growth trend for top match", { name: firstName })] : []),
|
|
60
|
+
action("sfr_get_property", "Full property details", { address: args.address }),
|
|
61
|
+
];
|
|
62
|
+
return structuredResult(lines.join("\n"), { items: slimBuyers, total: buyers.length, page: 1 }, actions);
|
|
34
63
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
66
|
+
const zipMatch = args.address.match(/\b(\d{5})\b/);
|
|
67
|
+
const zip = zipMatch?.[1];
|
|
68
|
+
return structuredResult(`## Best Buyers — ${args.address}\n\n` +
|
|
69
|
+
`_Property not found at this address. The address must exactly match the dataset ` +
|
|
70
|
+
`(including directional prefixes like 'E'/'W', abbreviations like 'St'/'Ave', and unit numbers). ` +
|
|
71
|
+
`Use sfr_search_properties with the zip code to find the canonical address._`, { address: args.address, error: "not_found" }, [action("sfr_search_properties", "Search by zip to find correct address", zip ? { search_type: "zip", zips: zip } : undefined)]);
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
39
74
|
}
|
|
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
75
|
});
|
|
59
76
|
}
|
|
60
77
|
//# sourceMappingURL=bestBuyers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bestBuyers.js","sourceRoot":"","sources":["../../../src/tools/sfr/bestBuyers.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,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"bestBuyers.js","sourceRoot":"","sources":["../../../src/tools/sfr/bestBuyers.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,10 +16,12 @@ export function registerBuyerProfileTool(server, sfr) {
|
|
|
16
16
|
"target property type, price range), geographic focus, related LLCs, and contact info. " +
|
|
17
17
|
"Includes flip vs hold percentages and target margin ranges. " +
|
|
18
18
|
"Returns relatedLLCs — use these names with sfr_flip_activity(buyer_name=) to find flip transactions across all entities. " +
|
|
19
|
+
"Note: relatedLLCs may return simplified/slug names rather than exact deed-recorded entity names for some investors. " +
|
|
19
20
|
"For iBuyer/flipper margin analysis, chain: buyer_profile → get relatedLLCs → sfr_flip_activity per LLC. " +
|
|
20
|
-
"
|
|
21
|
+
"Profiles exist for investors with significant recorded transaction volume (typically 50+ acquisitions). " +
|
|
22
|
+
"Smaller investors won't have profiles — use sfr_top_buyers to discover names. " +
|
|
21
23
|
"For acquisition trends over time, use sfr_buyer_growth. " +
|
|
22
|
-
"CHAIN: Use
|
|
24
|
+
"CHAIN: Use plr_borrower_profile with the investor name to check private lending activity and get contact info (requires PLR API key).",
|
|
23
25
|
inputSchema: Input,
|
|
24
26
|
}, async (args) => {
|
|
25
27
|
try {
|
|
@@ -108,7 +110,7 @@ export function registerBuyerProfileTool(server, sfr) {
|
|
|
108
110
|
const topZip = Array.isArray(data.portfolio?.topZipCodes) ? data.portfolio.topZipCodes[0] : undefined;
|
|
109
111
|
const actions = [
|
|
110
112
|
action("sfr_buyer_growth", "Acquisition trend over time", { name: args.name }),
|
|
111
|
-
action("
|
|
113
|
+
action("plr_borrower_profile", "Lending activity + contacts (PLR key required)", { borrowerName: args.name }),
|
|
112
114
|
...(topZip ? [action("sfr_zip_detail", "Deep-dive top zip", { zipCode: topZip })] : []),
|
|
113
115
|
...(topZip ? [action("sfr_search_properties", "Recent purchases in top zip", { search_type: "zip", zips: topZip })] : []),
|
|
114
116
|
];
|
|
@@ -152,8 +154,13 @@ export function registerBuyerProfileTool(server, sfr) {
|
|
|
152
154
|
if (err instanceof ApiError && err.status === 404) {
|
|
153
155
|
const slug = slugifyBuyerName(args.name);
|
|
154
156
|
return structuredResult(`## Investor Profile — ${args.name}\n\n` +
|
|
155
|
-
`_No profile found (slug: "${slug}").
|
|
156
|
-
`
|
|
157
|
+
`_No SFR profile found (slug: "${slug}"). SFR profiles exist only for large institutional investors ` +
|
|
158
|
+
`(typically 50+ acquisitions)._ ` +
|
|
159
|
+
`For smaller investors, plr_borrower_profile provides lending activity and contact info (phone, email, principals). ` +
|
|
160
|
+
`Requires a PLR API key — reach out at support@sfranalytics.com or chat at https://mcp.sfranalytics.com to get access.`, { name: args.name, slug, error: "not_found" }, [
|
|
161
|
+
action("plr_borrower_profile", "Check PLR for lending activity + contacts", { borrowerName: args.name }),
|
|
162
|
+
action("sfr_top_buyers", "Find institutional buyers with profiles"),
|
|
163
|
+
]);
|
|
157
164
|
}
|
|
158
165
|
throw err;
|
|
159
166
|
}
|