@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,71 @@
1
+ import snowflakeSdk from "snowflake-sdk";
2
+ // Disable OCSP check to avoid hanging in some environments
3
+ snowflakeSdk.configure({ ocspFailOpen: true });
4
+ let connection = null;
5
+ let config = null;
6
+ export function initSnowflake(cfg) {
7
+ config = cfg;
8
+ }
9
+ export function isSnowflakeConfigured() {
10
+ return config !== null;
11
+ }
12
+ function getConnection() {
13
+ return new Promise((resolve, reject) => {
14
+ if (connection && connection.isUp()) {
15
+ resolve(connection);
16
+ return;
17
+ }
18
+ if (!config) {
19
+ reject(new Error("Snowflake not configured. Call initSnowflake() first."));
20
+ return;
21
+ }
22
+ const conn = snowflakeSdk.createConnection({
23
+ account: config.account,
24
+ username: config.username,
25
+ password: config.password,
26
+ database: config.database,
27
+ schema: config.schema,
28
+ warehouse: config.warehouse,
29
+ });
30
+ conn.connect((err) => {
31
+ if (err) {
32
+ reject(new Error(`Snowflake connection failed: ${err.message}`));
33
+ }
34
+ else {
35
+ connection = conn;
36
+ resolve(conn);
37
+ }
38
+ });
39
+ });
40
+ }
41
+ export async function sfQuery(sql, binds = []) {
42
+ const conn = await getConnection();
43
+ return new Promise((resolve, reject) => {
44
+ conn.execute({
45
+ sqlText: sql,
46
+ binds,
47
+ complete: (err, _stmt, rows) => {
48
+ if (err) {
49
+ reject(new Error(`Snowflake query failed: ${err.message}`));
50
+ }
51
+ else {
52
+ resolve((rows ?? []));
53
+ }
54
+ },
55
+ });
56
+ });
57
+ }
58
+ export async function disconnectSnowflake() {
59
+ if (!connection)
60
+ return;
61
+ return new Promise((resolve, reject) => {
62
+ connection.destroy((err) => {
63
+ connection = null;
64
+ if (err)
65
+ reject(new Error(`Snowflake disconnect failed: ${err.message}`));
66
+ else
67
+ resolve();
68
+ });
69
+ });
70
+ }
71
+ //# sourceMappingURL=snowflake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snowflake.js","sourceRoot":"","sources":["../../src/services/snowflake.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAWzC,2DAA2D;AAC3D,YAAY,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/C,IAAI,UAAU,GAAmC,IAAI,CAAC;AACtD,IAAI,MAAM,GAA2B,IAAI,CAAC;AAE1C,MAAM,UAAU,aAAa,CAAC,GAAoB;IAChD,MAAM,GAAG,GAAG,CAAC;AACf,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,MAAM,KAAK,IAAI,CAAC;AACzB,CAAC;AAED,SAAS,aAAa;IACpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,OAAO,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,CAAC,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC,CAAC;YAC3E,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,YAAY,CAAC,gBAAgB,CAAC;YACzC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,CAAC,GAAsB,EAAE,EAAE;YACtC,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACnE,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAW,EACX,QAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,MAAM,aAAa,EAAE,CAAC;IACnC,OAAO,IAAI,OAAO,CAAM,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,IAAI,CAAC,OAAO,CAAC;YACX,OAAO,EAAE,GAAG;YACZ,KAAK;YACL,QAAQ,EAAE,CAAC,GAAsB,EAAE,KAAc,EAAE,IAA2B,EAAE,EAAE;gBAChF,IAAI,GAAG,EAAE,CAAC;oBACR,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAC9D,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,CAAC,IAAI,IAAI,EAAE,CAAQ,CAAC,CAAC;gBAC/B,CAAC;YACH,CAAC;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,UAAW,CAAC,OAAO,CAAC,CAAC,GAAsB,EAAE,EAAE;YAC7C,UAAU,GAAG,IAAI,CAAC;YAClB,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;;gBACrE,OAAO,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Auto-fill missing SFR date pair.
3
+ *
4
+ * Without dates the SFR API returns all-time data, which is often too
5
+ * broad (e.g. top buyers across 10+ years). Default to last 1 year so
6
+ * results reflect recent activity.
7
+ *
8
+ * - Neither provided → default to last 1 year
9
+ * - Min without max → default max to today
10
+ * - Max without min → default min to 1 year before max
11
+ * - Invalid date values → treated as unset (fall through to defaults)
12
+ */
13
+ export declare function autoFillDateRange(params: Record<string, unknown>, minKey?: string, maxKey?: string): void;
14
+ /**
15
+ * Auto-fill PLR start_date/end_date when NEITHER is provided.
16
+ *
17
+ * The PLR backend defaults to a 10-year window (MAX_DATE_RANGE_DAYS=3650)
18
+ * when no dates are given. Since pl_base data only goes back to 2021,
19
+ * the "previous" comparison period (2006-2016) has zero rows, causing
20
+ * period-over-period endpoints (msa_rankings, borrower_rankings, etc.)
21
+ * to return 0 results.
22
+ *
23
+ * Default: last 1 year → recent period has data AND previous period
24
+ * (the year before) also has data.
25
+ */
26
+ export declare function autoFillPlrDates(params: Record<string, unknown>, startKey?: string, endKey?: string): void;
@@ -0,0 +1,86 @@
1
+ /** Check if a string is a valid YYYY-MM-DD date. */
2
+ function isValidDateStr(v) {
3
+ if (typeof v !== "string")
4
+ return false;
5
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(v))
6
+ return false;
7
+ const d = new Date(v + "T00:00:00Z");
8
+ return !isNaN(d.getTime());
9
+ }
10
+ /**
11
+ * Auto-fill missing SFR date pair.
12
+ *
13
+ * Without dates the SFR API returns all-time data, which is often too
14
+ * broad (e.g. top buyers across 10+ years). Default to last 1 year so
15
+ * results reflect recent activity.
16
+ *
17
+ * - Neither provided → default to last 1 year
18
+ * - Min without max → default max to today
19
+ * - Max without min → default min to 1 year before max
20
+ * - Invalid date values → treated as unset (fall through to defaults)
21
+ */
22
+ export function autoFillDateRange(params, minKey = "sales_date_min", maxKey = "sales_date_max") {
23
+ // Treat invalid dates as unset
24
+ if (params[minKey] !== undefined && !isValidDateStr(params[minKey])) {
25
+ delete params[minKey];
26
+ }
27
+ if (params[maxKey] !== undefined && !isValidDateStr(params[maxKey])) {
28
+ delete params[maxKey];
29
+ }
30
+ const hasMin = params[minKey] !== undefined;
31
+ const hasMax = params[maxKey] !== undefined;
32
+ if (!hasMin && !hasMax) {
33
+ const now = new Date();
34
+ params[maxKey] = now.toISOString().slice(0, 10);
35
+ const oneYearAgo = new Date(now);
36
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
37
+ params[minKey] = oneYearAgo.toISOString().slice(0, 10);
38
+ }
39
+ else if (hasMin && !hasMax) {
40
+ params[maxKey] = new Date().toISOString().slice(0, 10);
41
+ }
42
+ else if (hasMax && !hasMin) {
43
+ const max = new Date(params[maxKey]);
44
+ max.setFullYear(max.getFullYear() - 1);
45
+ params[minKey] = max.toISOString().slice(0, 10);
46
+ }
47
+ }
48
+ /**
49
+ * Auto-fill PLR start_date/end_date when NEITHER is provided.
50
+ *
51
+ * The PLR backend defaults to a 10-year window (MAX_DATE_RANGE_DAYS=3650)
52
+ * when no dates are given. Since pl_base data only goes back to 2021,
53
+ * the "previous" comparison period (2006-2016) has zero rows, causing
54
+ * period-over-period endpoints (msa_rankings, borrower_rankings, etc.)
55
+ * to return 0 results.
56
+ *
57
+ * Default: last 1 year → recent period has data AND previous period
58
+ * (the year before) also has data.
59
+ */
60
+ export function autoFillPlrDates(params, startKey = "start_date", endKey = "end_date") {
61
+ // Treat invalid dates as unset
62
+ if (params[startKey] !== undefined && !isValidDateStr(params[startKey])) {
63
+ delete params[startKey];
64
+ }
65
+ if (params[endKey] !== undefined && !isValidDateStr(params[endKey])) {
66
+ delete params[endKey];
67
+ }
68
+ const hasStart = params[startKey] !== undefined;
69
+ const hasEnd = params[endKey] !== undefined;
70
+ if (!hasStart && !hasEnd) {
71
+ const now = new Date();
72
+ params[endKey] = now.toISOString().slice(0, 10);
73
+ const oneYearAgo = new Date(now);
74
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
75
+ params[startKey] = oneYearAgo.toISOString().slice(0, 10);
76
+ }
77
+ else if (hasStart && !hasEnd) {
78
+ params[endKey] = new Date().toISOString().slice(0, 10);
79
+ }
80
+ else if (hasEnd && !hasStart) {
81
+ const end = new Date(params[endKey]);
82
+ end.setFullYear(end.getFullYear() - 1);
83
+ params[startKey] = end.toISOString().slice(0, 10);
84
+ }
85
+ }
86
+ //# sourceMappingURL=dateHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateHelper.js","sourceRoot":"","sources":["../../src/tools/dateHelper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { NextAction } from "./nextActions.js";
2
+ /** Truncate JSON to a max length for inclusion in tool responses */
3
+ export declare function truncatedJson(data: unknown, maxLen?: number): string;
4
+ /** Format a key-value summary as markdown */
5
+ export declare function kvSummary(pairs: [string, unknown][]): string;
6
+ /** Format an array of records as a compact markdown table */
7
+ export declare function markdownTable(headers: string[], rows: (string | number | null | undefined)[][]): string;
8
+ /** Build a standard MCP text response */
9
+ export declare function textContent(text: string): {
10
+ content: {
11
+ type: "text";
12
+ text: string;
13
+ }[];
14
+ };
15
+ /** Build a response with both human-readable text and machine-readable structured data */
16
+ export declare function structuredResult(text: string, data: Record<string, unknown>, nextActions?: NextAction[]): {
17
+ content: {
18
+ type: "text";
19
+ text: string;
20
+ }[];
21
+ structuredContent: Record<string, unknown>;
22
+ };
23
+ /** Build an error response for tool handlers */
24
+ export declare function errorContent(message: string): {
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ isError: boolean;
30
+ };
31
+ /**
32
+ * Format an API error into a standard error response.
33
+ * Replaces the duplicated catch block pattern across all tool files.
34
+ */
35
+ export declare function handleToolError(err: unknown, toolName: string): {
36
+ content: {
37
+ type: "text";
38
+ text: string;
39
+ }[];
40
+ isError: boolean;
41
+ };
42
+ /** Format a dollar amount with adaptive B/M/K suffix */
43
+ export declare function fmtDollars(v: unknown): string;
44
+ /** Format a number as a percentage with 1 decimal, e.g. 11.94 → "11.9%" */
45
+ export declare function fmtPercent(v: unknown): string;
46
+ /** Format an ISO timestamp into a readable period label */
47
+ export declare function fmtPeriod(key: string, v: string): string;
48
+ /** Copy all defined values from args into a flat query object. */
49
+ export declare function argsToQuery(args: Record<string, unknown>): Record<string, unknown>;
50
+ export type FieldSpec = {
51
+ /** API field name */
52
+ key: string;
53
+ /** Display label */
54
+ label: string;
55
+ /** Optional formatter */
56
+ fmt?: (v: unknown) => string;
57
+ };
58
+ /**
59
+ * Map API response fields to display pairs, tracking unmapped keys.
60
+ * Returns { pairs, unmapped } for downstream drift detection.
61
+ * Adoption across tools is deferred to a separate PR.
62
+ */
63
+ export declare function mapWithCoverage(data: Record<string, unknown>, specs: FieldSpec[], ignoreKeys?: string[]): {
64
+ pairs: [string, unknown][];
65
+ unmapped: string[];
66
+ };
@@ -0,0 +1,219 @@
1
+ import { ApiError } from "../services/httpClient.js";
2
+ /** Truncate JSON to a max length for inclusion in tool responses */
3
+ export function truncatedJson(data, maxLen = 5000) {
4
+ const raw = JSON.stringify(data, null, 2) ?? "null";
5
+ if (raw.length <= maxLen)
6
+ return raw;
7
+ return raw.slice(0, maxLen) + "\n... (truncated)";
8
+ }
9
+ /** Format a key-value summary as markdown */
10
+ export function kvSummary(pairs) {
11
+ return pairs
12
+ .filter(([, v]) => v != null && v !== "")
13
+ .map(([k, v]) => `**${k}:** ${v}`)
14
+ .join("\n");
15
+ }
16
+ /** Escape a cell value for safe inclusion in a markdown table */
17
+ function escapeCell(v) {
18
+ if (v == null)
19
+ return "—";
20
+ return String(v).replace(/\|/g, "\\|").replace(/\n/g, " ");
21
+ }
22
+ /** Format an array of records as a compact markdown table */
23
+ export function markdownTable(headers, rows) {
24
+ if (rows.length === 0)
25
+ return "_No results_";
26
+ const headerRow = `| ${headers.map(escapeCell).join(" | ")} |`;
27
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
28
+ const dataRows = rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`);
29
+ return [headerRow, separator, ...dataRows].join("\n");
30
+ }
31
+ /** Build a standard MCP text response */
32
+ export function textContent(text) {
33
+ return { content: [{ type: "text", text }] };
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Structured output size management
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Max characters for structuredContent JSON (roughly ~12K tokens).
40
+ * Claude Code warns at 10K tokens and hard-limits at 25K tokens for MCP
41
+ * tool results. Since both content + structuredContent may reach the model,
42
+ * we budget ~12K tokens for structured and ~6K for markdown.
43
+ */
44
+ const MAX_STRUCTURED_CHARS = 50_000;
45
+ /** Max items in any array within structuredContent */
46
+ const MAX_ARRAY_ITEMS = 50;
47
+ /** Max nesting depth before truncation */
48
+ const MAX_DEPTH = 8;
49
+ /** Max string length for any single field */
50
+ const MAX_STRING_CHARS = 2000;
51
+ /**
52
+ * Recursively enforce size limits on a JSON-serializable value.
53
+ * Caps arrays, string lengths, and nesting depth.
54
+ * Mutates the report object to track what was truncated.
55
+ */
56
+ function truncateValue(value, depth, report) {
57
+ if (depth > MAX_DEPTH) {
58
+ report.truncated = true;
59
+ if (!report.reasons.includes("maxDepth"))
60
+ report.reasons.push("maxDepth");
61
+ return "[truncated: max depth]";
62
+ }
63
+ if (typeof value === "string" && value.length > MAX_STRING_CHARS) {
64
+ report.truncated = true;
65
+ if (!report.reasons.includes("maxStringLength"))
66
+ report.reasons.push("maxStringLength");
67
+ return value.slice(0, MAX_STRING_CHARS) + "...[truncated]";
68
+ }
69
+ if (Array.isArray(value)) {
70
+ const capped = value.length > MAX_ARRAY_ITEMS;
71
+ if (capped) {
72
+ report.truncated = true;
73
+ if (!report.reasons.includes("maxArrayItems"))
74
+ report.reasons.push("maxArrayItems");
75
+ }
76
+ const slice = capped ? value.slice(0, MAX_ARRAY_ITEMS) : value;
77
+ const mapped = slice.map((v) => truncateValue(v, depth + 1, report));
78
+ if (capped) {
79
+ mapped.push({ _truncated: true, _total: value.length, _returned: MAX_ARRAY_ITEMS });
80
+ }
81
+ return mapped;
82
+ }
83
+ if (value && typeof value === "object") {
84
+ const out = {};
85
+ for (const [k, v] of Object.entries(value)) {
86
+ out[k] = truncateValue(v, depth + 1, report);
87
+ }
88
+ return out;
89
+ }
90
+ return value;
91
+ }
92
+ /**
93
+ * Enforce size limits on structuredContent data.
94
+ * Returns the (possibly truncated) data. Adds _outputMeta if truncation occurred.
95
+ */
96
+ function capStructuredOutput(data) {
97
+ // Quick check: if small enough, return as-is
98
+ const raw = JSON.stringify(data);
99
+ if (raw.length <= MAX_STRUCTURED_CHARS)
100
+ return data;
101
+ // Apply recursive truncation
102
+ const report = { truncated: false, reasons: ["outputSizeLimit"] };
103
+ const capped = truncateValue(data, 0, report);
104
+ capped._outputMeta = {
105
+ truncated: true,
106
+ originalSizeChars: raw.length,
107
+ cappedSizeChars: JSON.stringify(capped).length,
108
+ reasons: report.reasons,
109
+ hint: "Result was truncated to stay within MCP output limits. Use pagination params or narrow your query for complete data.",
110
+ };
111
+ return capped;
112
+ }
113
+ /** Build a response with both human-readable text and machine-readable structured data */
114
+ export function structuredResult(text, data, nextActions) {
115
+ const structured = capStructuredOutput(data);
116
+ if (nextActions?.length)
117
+ structured.nextActions = nextActions;
118
+ return {
119
+ content: [{ type: "text", text }],
120
+ structuredContent: structured,
121
+ };
122
+ }
123
+ /** Build an error response for tool handlers */
124
+ export function errorContent(message) {
125
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
126
+ }
127
+ /** Strip stack traces and internal file paths from error details. */
128
+ function sanitizeErrorDetail(raw, maxLen = 500) {
129
+ return raw
130
+ .replace(/\s+at\s+.+\(.+\)/g, "") // JS stack trace lines
131
+ .replace(/\/[^\s:]+\.[jt]s:\d+/g, "") // JS file paths
132
+ .replace(/File\s+"[^"]+",\s+line\s+\d+[^\n]*/g, "") // Python traceback lines
133
+ .replace(/\/[^\s:]+\.py:\d+/g, "") // Python file paths
134
+ .replace(/Traceback \(most recent call last\):[\s\S]*/g, "") // Full Python traceback block
135
+ .slice(0, maxLen);
136
+ }
137
+ /**
138
+ * Format an API error into a standard error response.
139
+ * Replaces the duplicated catch block pattern across all tool files.
140
+ */
141
+ export function handleToolError(err, toolName) {
142
+ let detail;
143
+ if (err instanceof ApiError) {
144
+ const raw = JSON.stringify(err.details);
145
+ detail = `HTTP ${err.status} - ${sanitizeErrorDetail(raw)}`;
146
+ }
147
+ else if (err instanceof Error) {
148
+ detail = sanitizeErrorDetail(err.message);
149
+ }
150
+ else {
151
+ detail = sanitizeErrorDetail(String(err));
152
+ }
153
+ return errorContent(`${toolName} failed: ${detail}`);
154
+ }
155
+ /** Format a dollar amount with adaptive B/M/K suffix */
156
+ export function fmtDollars(v) {
157
+ if (v == null)
158
+ return "—";
159
+ const n = Number(v);
160
+ if (isNaN(n))
161
+ return "—";
162
+ if (Math.abs(n) >= 1e9)
163
+ return `$${(n / 1e9).toFixed(2)}B`;
164
+ if (Math.abs(n) >= 1e6)
165
+ return `$${(n / 1e6).toFixed(1)}M`;
166
+ if (Math.abs(n) >= 1e3)
167
+ return `$${(n / 1e3).toFixed(0)}K`;
168
+ return `$${n.toLocaleString()}`;
169
+ }
170
+ /** Format a number as a percentage with 1 decimal, e.g. 11.94 → "11.9%" */
171
+ export function fmtPercent(v) {
172
+ if (v == null)
173
+ return "—";
174
+ const n = Number(v);
175
+ if (isNaN(n))
176
+ return "—";
177
+ return `${n.toFixed(1)}%`;
178
+ }
179
+ /** Format an ISO timestamp into a readable period label */
180
+ export function fmtPeriod(key, v) {
181
+ const d = new Date(v);
182
+ if (isNaN(d.getTime()))
183
+ return v;
184
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
185
+ if (/quarter/i.test(key)) {
186
+ const q = Math.ceil((d.getMonth() + 1) / 3);
187
+ return `Q${q} ${d.getFullYear()}`;
188
+ }
189
+ return `${months[d.getMonth()]} ${d.getFullYear()}`;
190
+ }
191
+ /** Copy all defined values from args into a flat query object. */
192
+ export function argsToQuery(args) {
193
+ const query = {};
194
+ for (const [key, val] of Object.entries(args)) {
195
+ if (val !== undefined)
196
+ query[key] = val;
197
+ }
198
+ return query;
199
+ }
200
+ /**
201
+ * Map API response fields to display pairs, tracking unmapped keys.
202
+ * Returns { pairs, unmapped } for downstream drift detection.
203
+ * Adoption across tools is deferred to a separate PR.
204
+ */
205
+ export function mapWithCoverage(data, specs, ignoreKeys = []) {
206
+ const mapped = new Set();
207
+ const pairs = [];
208
+ for (const spec of specs) {
209
+ const val = data[spec.key];
210
+ mapped.add(spec.key);
211
+ if (val == null || val === "")
212
+ continue;
213
+ pairs.push([spec.label, spec.fmt ? spec.fmt(val) : val]);
214
+ }
215
+ const ignoreSet = new Set(ignoreKeys);
216
+ const unmapped = Object.keys(data).filter((k) => !mapped.has(k) && !ignoreSet.has(k) && data[k] != null);
217
+ return { pairs, unmapped };
218
+ }
219
+ //# sourceMappingURL=formatters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../../src/tools/formatters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SfrClient } from "../services/sfr.js";
3
+ import type { PlrClient } from "../services/plr.js";
4
+ import type { AppConfig } from "../config.js";
5
+ export declare function registerHealthTool(server: McpServer, sfr: SfrClient | null, plr: PlrClient | null, config: AppConfig): void;
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { structuredResult } from "./formatters.js";
3
+ import { registerToolSafe } from "./registerToolSafe.js";
4
+ export function registerHealthTool(server, sfr, plr, config) {
5
+ registerToolSafe(server, "sfra_health", {
6
+ title: "SFRA MCP health check",
7
+ description: "Check connectivity to all configured APIs. " +
8
+ "Returns status and latency for each service. " +
9
+ "Use this to verify your API tokens are working.",
10
+ inputSchema: z.object({}),
11
+ }, async () => {
12
+ const lines = ["## SFRA MCP Health Check", ""];
13
+ const structured = {};
14
+ // Run health checks in parallel when both are configured
15
+ const [sfrHealth, plrHealth] = await Promise.all([
16
+ sfr && config.sfr ? sfr.healthCheck() : null,
17
+ plr && config.plr ? plr.healthCheck() : null,
18
+ ]);
19
+ if (sfrHealth) {
20
+ lines.push(`**SFR API**`, ` Status: ${sfrHealth.ok ? "OK" : "FAILED"}`, ` Latency: ${sfrHealth.latencyMs}ms`, "");
21
+ structured.sfr = { ok: sfrHealth.ok, latencyMs: sfrHealth.latencyMs };
22
+ }
23
+ else {
24
+ lines.push("**SFR API** — not configured", " Add SFR_API_TOKEN to enable 21 property, buyer, and market tools", "");
25
+ structured.sfr = { configured: false };
26
+ }
27
+ if (plrHealth) {
28
+ lines.push(`**PLR API**`, ` Status: ${plrHealth.ok ? "OK" : "FAILED"}`, ` Latency: ${plrHealth.latencyMs}ms`);
29
+ structured.plr = { ok: plrHealth.ok, latencyMs: plrHealth.latencyMs };
30
+ }
31
+ else {
32
+ lines.push("**PLR API** — not configured", " Add PLR_API_TOKEN to enable 15 lending and borrower tools");
33
+ structured.plr = { configured: false };
34
+ }
35
+ return structuredResult(lines.join("\n"), structured);
36
+ });
37
+ }
38
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/tools/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,UAAU,kBAAkB,CAChC,MAAiB,EACjB,GAAqB,EACrB,GAAqB,EACrB,MAAiB;IAEjB,gBAAgB,CAAC,MAAM,EACrB,aAAa,EACb;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,6CAA6C;YAC7C,+CAA+C;YAC/C,iDAAiD;QACnD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;KAC1B,EACD,KAAK,IAAI,EAAE;QACT,MAAM,KAAK,GAAa,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,UAAU,GAA4B,EAAE,CAAC;QAE/C,yDAAyD;QACzD,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC/C,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;YAC5C,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;SAC7C,CAAC,CAAC;QAEH,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CACR,aAAa,EACb,aAAa,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,EAC7C,cAAc,SAAS,CAAC,SAAS,IAAI,EACrC,EAAE,CACH,CAAC;YACF,UAAU,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CACR,8BAA8B,EAC9B,oEAAoE,EACpE,EAAE,CACH,CAAC;YACF,UAAU,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACzC,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CACR,aAAa,EACb,aAAa,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,EAC7C,cAAc,SAAS,CAAC,SAAS,IAAI,CACtC,CAAC;YACF,UAAU,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CACR,8BAA8B,EAC9B,6DAA6D,CAC9D,CAAC;YACF,UAAU,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;QACzC,CAAC;QAED,OAAO,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;IACxD,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Structured next-action hint attached to tool output */
2
+ export interface NextAction {
3
+ tool: string;
4
+ why: string;
5
+ args?: Record<string, unknown>;
6
+ }
7
+ /** Create a NextAction, omitting args when empty */
8
+ export declare function action(tool: string, why: string, args?: Record<string, unknown>): NextAction;
9
+ /** Extract common SFR location args for passing to other tools */
10
+ export declare function locationArgs(args: Record<string, unknown>): Record<string, string>;
@@ -0,0 +1,23 @@
1
+ /** Create a NextAction, omitting args when empty */
2
+ export function action(tool, why, args) {
3
+ const a = { tool, why };
4
+ if (args && Object.keys(args).length > 0)
5
+ a.args = args;
6
+ return a;
7
+ }
8
+ /** Extract common SFR location args for passing to other tools */
9
+ export function locationArgs(args) {
10
+ const loc = {};
11
+ if (args.search_type)
12
+ loc.search_type = String(args.search_type);
13
+ if (args.state)
14
+ loc.state = String(args.state);
15
+ if (args.city)
16
+ loc.city = String(args.city);
17
+ if (args.zips)
18
+ loc.zips = String(args.zips);
19
+ if (args.msa)
20
+ loc.msa = String(args.msa);
21
+ return loc;
22
+ }
23
+ //# sourceMappingURL=nextActions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextActions.js","sourceRoot":"","sources":["../../src/tools/nextActions.ts"],"names":[],"mappings":"AAOA,oDAAoD;AACpD,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,GAAW,EAAE,IAA8B;IAC9E,MAAM,CAAC,GAAe,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACpC,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,IAAI,CAAC,WAAW;QAAE,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,IAAI,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,CAAC,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,CAAC,GAAG;QAAE,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,GAAG,CAAC;AACb,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 registerBorrowerContactsTool(server: McpServer, plr: PlrClient): void;