@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.
- package/LICENSE +24 -0
- package/README.md +147 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +252 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +213 -0
- package/dist/server.js.map +1 -0
- package/dist/services/httpClient.d.ts +19 -0
- package/dist/services/httpClient.js +73 -0
- package/dist/services/httpClient.js.map +1 -0
- package/dist/services/plr.d.ts +26 -0
- package/dist/services/plr.js +75 -0
- package/dist/services/plr.js.map +1 -0
- package/dist/services/sfr.d.ts +33 -0
- package/dist/services/sfr.js +124 -0
- package/dist/services/sfr.js.map +1 -0
- package/dist/services/snowflake.d.ts +12 -0
- package/dist/services/snowflake.js +71 -0
- package/dist/services/snowflake.js.map +1 -0
- package/dist/tools/dateHelper.d.ts +26 -0
- package/dist/tools/dateHelper.js +86 -0
- package/dist/tools/dateHelper.js.map +1 -0
- package/dist/tools/formatters.d.ts +66 -0
- package/dist/tools/formatters.js +219 -0
- package/dist/tools/formatters.js.map +1 -0
- package/dist/tools/health.d.ts +5 -0
- package/dist/tools/health.js +38 -0
- package/dist/tools/health.js.map +1 -0
- package/dist/tools/nextActions.d.ts +10 -0
- package/dist/tools/nextActions.js +23 -0
- package/dist/tools/nextActions.js.map +1 -0
- package/dist/tools/plr/borrowerContacts.d.ts +3 -0
- package/dist/tools/plr/borrowerContacts.js +73 -0
- package/dist/tools/plr/borrowerContacts.js.map +1 -0
- package/dist/tools/plr/borrowerLoans.d.ts +3 -0
- package/dist/tools/plr/borrowerLoans.js +115 -0
- package/dist/tools/plr/borrowerLoans.js.map +1 -0
- package/dist/tools/plr/borrowerProfile.d.ts +3 -0
- package/dist/tools/plr/borrowerProfile.js +201 -0
- package/dist/tools/plr/borrowerProfile.js.map +1 -0
- package/dist/tools/plr/borrowerRankings.d.ts +3 -0
- package/dist/tools/plr/borrowerRankings.js +123 -0
- package/dist/tools/plr/borrowerRankings.js.map +1 -0
- package/dist/tools/plr/borrowerSearch.d.ts +3 -0
- package/dist/tools/plr/borrowerSearch.js +128 -0
- package/dist/tools/plr/borrowerSearch.js.map +1 -0
- package/dist/tools/plr/churnedBorrowers.d.ts +3 -0
- package/dist/tools/plr/churnedBorrowers.js +86 -0
- package/dist/tools/plr/churnedBorrowers.js.map +1 -0
- package/dist/tools/plr/lenderBorrowers.d.ts +3 -0
- package/dist/tools/plr/lenderBorrowers.js +71 -0
- package/dist/tools/plr/lenderBorrowers.js.map +1 -0
- package/dist/tools/plr/lenderRankings.d.ts +3 -0
- package/dist/tools/plr/lenderRankings.js +74 -0
- package/dist/tools/plr/lenderRankings.js.map +1 -0
- package/dist/tools/plr/loansNearby.d.ts +3 -0
- package/dist/tools/plr/loansNearby.js +113 -0
- package/dist/tools/plr/loansNearby.js.map +1 -0
- package/dist/tools/plr/marketTrends.d.ts +3 -0
- package/dist/tools/plr/marketTrends.js +95 -0
- package/dist/tools/plr/marketTrends.js.map +1 -0
- package/dist/tools/plr/msaRankings.d.ts +3 -0
- package/dist/tools/plr/msaRankings.js +74 -0
- package/dist/tools/plr/msaRankings.js.map +1 -0
- package/dist/tools/plr/negativeRemarks.d.ts +3 -0
- package/dist/tools/plr/negativeRemarks.js +94 -0
- package/dist/tools/plr/negativeRemarks.js.map +1 -0
- package/dist/tools/plr/ownerSearch.d.ts +3 -0
- package/dist/tools/plr/ownerSearch.js +57 -0
- package/dist/tools/plr/ownerSearch.js.map +1 -0
- package/dist/tools/plr/portfolioSummary.d.ts +3 -0
- package/dist/tools/plr/portfolioSummary.js +99 -0
- package/dist/tools/plr/portfolioSummary.js.map +1 -0
- package/dist/tools/plr/topBorrowers.d.ts +3 -0
- package/dist/tools/plr/topBorrowers.js +69 -0
- package/dist/tools/plr/topBorrowers.js.map +1 -0
- package/dist/tools/plr/topLenders.d.ts +3 -0
- package/dist/tools/plr/topLenders.js +75 -0
- package/dist/tools/plr/topLenders.js.map +1 -0
- package/dist/tools/plr/transactionHistory.d.ts +3 -0
- package/dist/tools/plr/transactionHistory.js +74 -0
- package/dist/tools/plr/transactionHistory.js.map +1 -0
- package/dist/tools/prompts.d.ts +7 -0
- package/dist/tools/prompts.js +157 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/registerToolSafe.d.ts +29 -0
- package/dist/tools/registerToolSafe.js +36 -0
- package/dist/tools/registerToolSafe.js.map +1 -0
- package/dist/tools/sfr/activityHighlights.d.ts +3 -0
- package/dist/tools/sfr/activityHighlights.js +70 -0
- package/dist/tools/sfr/activityHighlights.js.map +1 -0
- package/dist/tools/sfr/bestBuyers.d.ts +3 -0
- package/dist/tools/sfr/bestBuyers.js +60 -0
- package/dist/tools/sfr/bestBuyers.js.map +1 -0
- package/dist/tools/sfr/buyerGrowth.d.ts +3 -0
- package/dist/tools/sfr/buyerGrowth.js +68 -0
- package/dist/tools/sfr/buyerGrowth.js.map +1 -0
- package/dist/tools/sfr/buyerProfile.d.ts +3 -0
- package/dist/tools/sfr/buyerProfile.js +162 -0
- package/dist/tools/sfr/buyerProfile.js.map +1 -0
- package/dist/tools/sfr/distressSearch.d.ts +3 -0
- package/dist/tools/sfr/distressSearch.js +173 -0
- package/dist/tools/sfr/distressSearch.js.map +1 -0
- package/dist/tools/sfr/flipActivity.d.ts +3 -0
- package/dist/tools/sfr/flipActivity.js +110 -0
- package/dist/tools/sfr/flipActivity.js.map +1 -0
- package/dist/tools/sfr/flipStats.d.ts +3 -0
- package/dist/tools/sfr/flipStats.js +98 -0
- package/dist/tools/sfr/flipStats.js.map +1 -0
- package/dist/tools/sfr/getProperty.d.ts +3 -0
- package/dist/tools/sfr/getProperty.js +142 -0
- package/dist/tools/sfr/getProperty.js.map +1 -0
- package/dist/tools/sfr/institutionalOwners.d.ts +3 -0
- package/dist/tools/sfr/institutionalOwners.js +88 -0
- package/dist/tools/sfr/institutionalOwners.js.map +1 -0
- package/dist/tools/sfr/investorActivity.d.ts +3 -0
- package/dist/tools/sfr/investorActivity.js +130 -0
- package/dist/tools/sfr/investorActivity.js.map +1 -0
- package/dist/tools/sfr/marketHighlights.d.ts +3 -0
- package/dist/tools/sfr/marketHighlights.js +100 -0
- package/dist/tools/sfr/marketHighlights.js.map +1 -0
- package/dist/tools/sfr/msaResolver.d.ts +15 -0
- package/dist/tools/sfr/msaResolver.js +109 -0
- package/dist/tools/sfr/msaResolver.js.map +1 -0
- package/dist/tools/sfr/propertyBatch.d.ts +3 -0
- package/dist/tools/sfr/propertyBatch.js +73 -0
- package/dist/tools/sfr/propertyBatch.js.map +1 -0
- package/dist/tools/sfr/propertyComps.d.ts +3 -0
- package/dist/tools/sfr/propertyComps.js +56 -0
- package/dist/tools/sfr/propertyComps.js.map +1 -0
- package/dist/tools/sfr/propertyTransactions.d.ts +3 -0
- package/dist/tools/sfr/propertyTransactions.js +50 -0
- package/dist/tools/sfr/propertyTransactions.js.map +1 -0
- package/dist/tools/sfr/rentalComparables.d.ts +3 -0
- package/dist/tools/sfr/rentalComparables.js +91 -0
- package/dist/tools/sfr/rentalComparables.js.map +1 -0
- package/dist/tools/sfr/rentalMarketAnalysis.d.ts +3 -0
- package/dist/tools/sfr/rentalMarketAnalysis.js +134 -0
- package/dist/tools/sfr/rentalMarketAnalysis.js.map +1 -0
- package/dist/tools/sfr/rentalStats.d.ts +3 -0
- package/dist/tools/sfr/rentalStats.js +118 -0
- package/dist/tools/sfr/rentalStats.js.map +1 -0
- package/dist/tools/sfr/searchProperties.d.ts +3 -0
- package/dist/tools/sfr/searchProperties.js +157 -0
- package/dist/tools/sfr/searchProperties.js.map +1 -0
- package/dist/tools/sfr/topBuyers.d.ts +3 -0
- package/dist/tools/sfr/topBuyers.js +91 -0
- package/dist/tools/sfr/topBuyers.js.map +1 -0
- package/dist/tools/sfr/zipDetail.d.ts +3 -0
- package/dist/tools/sfr/zipDetail.js +85 -0
- package/dist/tools/sfr/zipDetail.js.map +1 -0
- package/dist/tools/sfr/zipFinder.d.ts +3 -0
- package/dist/tools/sfr/zipFinder.js +79 -0
- package/dist/tools/sfr/zipFinder.js.map +1 -0
- package/dist/tools/slugHelper.d.ts +2 -0
- package/dist/tools/slugHelper.js +5 -0
- package/dist/tools/slugHelper.js.map +1 -0
- package/dist/tools/snowflake/compareMarkets.d.ts +2 -0
- package/dist/tools/snowflake/compareMarkets.js +95 -0
- package/dist/tools/snowflake/compareMarkets.js.map +1 -0
- package/dist/tools/snowflake/hviTrend.d.ts +2 -0
- package/dist/tools/snowflake/hviTrend.js +77 -0
- package/dist/tools/snowflake/hviTrend.js.map +1 -0
- package/dist/tools/snowflake/investorActivity.d.ts +2 -0
- package/dist/tools/snowflake/investorActivity.js +138 -0
- package/dist/tools/snowflake/investorActivity.js.map +1 -0
- package/dist/tools/snowflake/marketSnapshot.d.ts +2 -0
- package/dist/tools/snowflake/marketSnapshot.js +185 -0
- package/dist/tools/snowflake/marketSnapshot.js.map +1 -0
- package/dist/tools/snowflake/marketTrends.d.ts +2 -0
- package/dist/tools/snowflake/marketTrends.js +81 -0
- package/dist/tools/snowflake/marketTrends.js.map +1 -0
- package/dist/tools/snowflake/rankRentalZips.d.ts +2 -0
- package/dist/tools/snowflake/rankRentalZips.js +94 -0
- package/dist/tools/snowflake/rankRentalZips.js.map +1 -0
- package/dist/tools/snowflake/rankZips.d.ts +2 -0
- package/dist/tools/snowflake/rankZips.js +86 -0
- package/dist/tools/snowflake/rankZips.js.map +1 -0
- package/dist/tools/snowflake/rentalMarket.d.ts +2 -0
- package/dist/tools/snowflake/rentalMarket.js +111 -0
- package/dist/tools/snowflake/rentalMarket.js.map +1 -0
- package/dist/tools/snowflake/rentalYield.d.ts +2 -0
- package/dist/tools/snowflake/rentalYield.js +143 -0
- package/dist/tools/snowflake/rentalYield.js.map +1 -0
- package/dist/tools/snowflake/zipProfile.d.ts +2 -0
- package/dist/tools/snowflake/zipProfile.js +110 -0
- package/dist/tools/snowflake/zipProfile.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2024-2026 SFR Analytics, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY LICENSE
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary property of SFR Analytics, Inc.
|
|
7
|
+
|
|
8
|
+
Use of this Software is governed by the SFR Analytics Terms of Service
|
|
9
|
+
available at: https://www.sfranalytics.com/terms
|
|
10
|
+
|
|
11
|
+
You may use this Software only in accordance with a valid SFR Analytics
|
|
12
|
+
subscription and the Terms of Service. You may not copy, modify, distribute,
|
|
13
|
+
sell, or sublicense this Software or any portion thereof without prior written
|
|
14
|
+
consent from SFR Analytics, Inc.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
SFR ANALYTICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
21
|
+
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For licensing inquiries: support@sfranalytics.com
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @sfranalytics/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [SFR Analytics](https://www.sfranalytics.com) — single-family rental property data, buyer intelligence, and private lending insights for AI assistants.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Claude Code
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
claude mcp add sfra -- npx -y @sfranalytics/mcp \
|
|
11
|
+
--env SFR_API_TOKEN=your-sfr-key \
|
|
12
|
+
--env PLR_API_TOKEN=your-plr-key
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to your `claude_desktop_config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"sfra": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@sfranalytics/mcp"],
|
|
25
|
+
"env": {
|
|
26
|
+
"SFR_API_TOKEN": "your-sfr-key",
|
|
27
|
+
"PLR_API_TOKEN": "your-plr-key"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Hosted HTTP Transport
|
|
35
|
+
|
|
36
|
+
Run the MCP server over HTTP:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev:http
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Connect from Claude Code:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
claude mcp add sfra-http --transport http http://localhost:3000/mcp \
|
|
46
|
+
--header "SFR-Api-Token: your-sfr-key" \
|
|
47
|
+
--header "PLR-Api-Token: your-plr-key"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Production endpoint example:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
claude mcp add sfra-http --transport http https://mcp.sfranalytics.com/mcp \
|
|
54
|
+
--header "SFR-Api-Token: your-sfr-key" \
|
|
55
|
+
--header "PLR-Api-Token: your-plr-key"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Notes:
|
|
59
|
+
|
|
60
|
+
- Provide at least one token header (`SFR-Api-Token` or `PLR-Api-Token`).
|
|
61
|
+
- `MCP_ALLOWED_ORIGINS` controls allowed `Origin` headers.
|
|
62
|
+
- `MCP_ALLOWED_HOSTS` controls allowed `Host` headers (recommended for public deployments).
|
|
63
|
+
- `MCP_STRICT_TOKEN_VALIDATION=true` optionally validates token(s) on each MCP request via upstream auth probes.
|
|
64
|
+
- In strict mode, explicit auth failures return `401`; temporary probe outages return `503`.
|
|
65
|
+
- Raw HTTP clients should send `Accept: application/json, text/event-stream`.
|
|
66
|
+
- HTTP mode currently excludes Snowflake tools.
|
|
67
|
+
|
|
68
|
+
## API Keys
|
|
69
|
+
|
|
70
|
+
You can configure one or both API keys. Tools are enabled based on which keys you provide.
|
|
71
|
+
|
|
72
|
+
| Key | Enables | Tools |
|
|
73
|
+
|-----|---------|-------|
|
|
74
|
+
| `SFR_API_TOKEN` | Property data, buyer intelligence, market analytics | 21 tools |
|
|
75
|
+
| `PLR_API_TOKEN` | Private lending, borrower/lender intelligence | 15 tools |
|
|
76
|
+
|
|
77
|
+
Get your API keys at [sfranalytics.com](https://www.sfranalytics.com).
|
|
78
|
+
|
|
79
|
+
## Tool Categories
|
|
80
|
+
|
|
81
|
+
### SFR API (21 tools)
|
|
82
|
+
|
|
83
|
+
| Category | Tools | Description |
|
|
84
|
+
|----------|-------|-------------|
|
|
85
|
+
| Property Research | 6 | Search transactions, property details, history, MLS listings, comparables, distress/foreclosure |
|
|
86
|
+
| Rental Analysis | 3 | Market-level rental stats, nearby rental comps, rent growth trends |
|
|
87
|
+
| Zip/Market Screening | 3 | Rank zips by yield/rent/value, zip deep-dives, institutional ownership |
|
|
88
|
+
| Buyer Intelligence | 5 | Top buyers, investor profiles, growth trends, best deals, market highlights |
|
|
89
|
+
| Market Activity | 4 | Investor transactions, flip stats, flip activity, activity snapshots |
|
|
90
|
+
|
|
91
|
+
### PLR API (15 tools)
|
|
92
|
+
|
|
93
|
+
| Category | Tools | Description |
|
|
94
|
+
|----------|-------|-------------|
|
|
95
|
+
| Borrower Intelligence | 6 | Search, profiles, rankings, top borrowers, loan history, contacts |
|
|
96
|
+
| Lender Intelligence | 4 | Rankings, top lenders, lender clients, churned borrowers |
|
|
97
|
+
| Market & Portfolio | 5 | Nationwide trends, MSA rankings, nearby loans, transaction feed, portfolio totals |
|
|
98
|
+
|
|
99
|
+
### Shared
|
|
100
|
+
|
|
101
|
+
| Tool | Description |
|
|
102
|
+
|------|-------------|
|
|
103
|
+
| `sfra_health` | Connectivity check for configured APIs |
|
|
104
|
+
|
|
105
|
+
## Configuration
|
|
106
|
+
|
|
107
|
+
| Variable | Required | Default | Description |
|
|
108
|
+
|----------|----------|---------|-------------|
|
|
109
|
+
| `SFR_API_TOKEN` | One or both | — | SFR Analytics API token |
|
|
110
|
+
| `PLR_API_TOKEN` | One or both | — | Private Lender Radar API token |
|
|
111
|
+
| `SFR_BASE_URL` | No | `https://api.sfranalytics.com` | SFR API base URL |
|
|
112
|
+
| `PLR_BASE_URL` | No | `https://radar-api.sfranalytics.com` | PLR API base URL |
|
|
113
|
+
| `HTTP_TIMEOUT_MS` | No | `20000` | HTTP request timeout (ms) |
|
|
114
|
+
| `PORT` | No | `3000` | HTTP server port (`dev:http` / `start:http`) |
|
|
115
|
+
| `MCP_BIND_HOST` | No | `127.0.0.1` | HTTP bind host |
|
|
116
|
+
| `MCP_ALLOWED_ORIGINS` | No | — | Comma-separated allowed `Origin` values for `/mcp` |
|
|
117
|
+
| `MCP_ALLOWED_HOSTS` | No | — | Comma-separated allowed `Host` values for DNS rebinding protection |
|
|
118
|
+
| `MCP_STRICT_TOKEN_VALIDATION` | No | `false` | If `true`, validate provided HTTP token(s) on each request using upstream auth probes |
|
|
119
|
+
| `MCP_SFR_TOKEN_PROBE_ADDRESS` | No | `123 Main St, Phoenix, AZ 85004` | Address used for SFR strict-token auth probe |
|
|
120
|
+
|
|
121
|
+
At least one API token must be provided.
|
|
122
|
+
|
|
123
|
+
## Example Prompts
|
|
124
|
+
|
|
125
|
+
The server includes built-in prompts for common workflows:
|
|
126
|
+
|
|
127
|
+
- **market-screen** — Find top zip codes by investment criteria
|
|
128
|
+
- **buyer-research** — Research a property investor's portfolio and strategy
|
|
129
|
+
- **property-analysis** — Evaluate a property for SFR investment
|
|
130
|
+
- **lending-overview** — Private lending market overview
|
|
131
|
+
- **borrower-outreach** — Build a targeted borrower outreach list
|
|
132
|
+
|
|
133
|
+
Prompts are scoped to your configured APIs.
|
|
134
|
+
|
|
135
|
+
## Requirements
|
|
136
|
+
|
|
137
|
+
- Node.js >= 18.0.0
|
|
138
|
+
- An MCP-compatible client (Claude Code, Claude Desktop, etc.)
|
|
139
|
+
|
|
140
|
+
## Support
|
|
141
|
+
|
|
142
|
+
- Email: support@sfranalytics.com
|
|
143
|
+
- Website: [sfranalytics.com](https://www.sfranalytics.com)
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
Proprietary. See [LICENSE](./LICENSE) and [Terms of Service](https://www.sfranalytics.com/terms).
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type ServiceConfig = {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiToken: string;
|
|
4
|
+
apiTokenHeader: string;
|
|
5
|
+
};
|
|
6
|
+
export type SnowflakeConfig = {
|
|
7
|
+
account: string;
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
database: string;
|
|
11
|
+
schema: string;
|
|
12
|
+
warehouse: string;
|
|
13
|
+
};
|
|
14
|
+
export type AppConfig = {
|
|
15
|
+
sfr: ServiceConfig | null;
|
|
16
|
+
plr: ServiceConfig | null;
|
|
17
|
+
snowflake: SnowflakeConfig | null;
|
|
18
|
+
http: {
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type HeaderConfigInput = {
|
|
23
|
+
sfrToken?: string;
|
|
24
|
+
plrToken?: string;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
};
|
|
27
|
+
export declare function loadConfig(): AppConfig;
|
|
28
|
+
export declare function configFromHeaders(input: HeaderConfigInput): AppConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
function env(name) {
|
|
5
|
+
const v = process.env[name];
|
|
6
|
+
return v && v.trim() ? v.trim() : undefined;
|
|
7
|
+
}
|
|
8
|
+
/** Optional local secrets file for dev convenience. Keep it out of git. */
|
|
9
|
+
function loadLocalSecrets() {
|
|
10
|
+
const filename = env("MCP_LOCAL_SECRETS") ??
|
|
11
|
+
path.join(os.homedir(), ".sfra-mcp.secrets.json");
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(filename))
|
|
14
|
+
return {};
|
|
15
|
+
const raw = fs.readFileSync(filename, "utf8");
|
|
16
|
+
const json = JSON.parse(raw);
|
|
17
|
+
return typeof json === "object" && json ? json : {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function loadConfig() {
|
|
24
|
+
const secrets = loadLocalSecrets();
|
|
25
|
+
const get = (name) => env(name) ?? secrets[name];
|
|
26
|
+
const sfrToken = get("SFR_API_TOKEN");
|
|
27
|
+
const plrToken = get("PLR_API_TOKEN");
|
|
28
|
+
const sfAccount = get("SNOWFLAKE_ACCOUNT");
|
|
29
|
+
const sfUser = get("SNOWFLAKE_USERNAME");
|
|
30
|
+
const sfPass = get("SNOWFLAKE_PASSWORD");
|
|
31
|
+
if (!sfrToken && !plrToken && !sfAccount) {
|
|
32
|
+
throw new Error([
|
|
33
|
+
"No API tokens configured. Set at least one:",
|
|
34
|
+
"",
|
|
35
|
+
" SFR_API_TOKEN — property data, buyer intelligence, market analytics (21 tools)",
|
|
36
|
+
" PLR_API_TOKEN — private lending, borrower/lender intelligence (15 tools)",
|
|
37
|
+
" SNOWFLAKE_ACCOUNT — Redfin market data, census, rental yield, investor activity (10 tools)",
|
|
38
|
+
"",
|
|
39
|
+
"Get your API keys at https://www.sfranalytics.com",
|
|
40
|
+
"Questions? support@sfranalytics.com",
|
|
41
|
+
].join("\n"));
|
|
42
|
+
}
|
|
43
|
+
const snowflake = sfAccount && sfUser && sfPass
|
|
44
|
+
? {
|
|
45
|
+
account: sfAccount,
|
|
46
|
+
username: sfUser,
|
|
47
|
+
password: sfPass,
|
|
48
|
+
database: get("SNOWFLAKE_DATABASE") ?? "ANALYTICS",
|
|
49
|
+
schema: get("SNOWFLAKE_SCHEMA") ?? "PUBLIC",
|
|
50
|
+
warehouse: get("SNOWFLAKE_WAREHOUSE") ?? "COMPUTE_WH",
|
|
51
|
+
}
|
|
52
|
+
: null;
|
|
53
|
+
return {
|
|
54
|
+
sfr: sfrToken
|
|
55
|
+
? {
|
|
56
|
+
baseUrl: (get("SFR_BASE_URL") ?? "https://api.sfranalytics.com").replace(/\/$/, ""),
|
|
57
|
+
apiToken: sfrToken,
|
|
58
|
+
apiTokenHeader: get("SFR_API_TOKEN_HEADER") ?? "X-API-TOKEN",
|
|
59
|
+
}
|
|
60
|
+
: null,
|
|
61
|
+
plr: plrToken
|
|
62
|
+
? {
|
|
63
|
+
baseUrl: (get("PLR_BASE_URL") ?? "https://radar-api.sfranalytics.com").replace(/\/$/, ""),
|
|
64
|
+
apiToken: plrToken,
|
|
65
|
+
apiTokenHeader: get("PLR_API_TOKEN_HEADER") ?? "X-API-Key",
|
|
66
|
+
}
|
|
67
|
+
: null,
|
|
68
|
+
snowflake,
|
|
69
|
+
http: {
|
|
70
|
+
timeoutMs: Number(get("HTTP_TIMEOUT_MS") ?? 20000),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function configFromHeaders(input) {
|
|
75
|
+
const get = (name) => env(name);
|
|
76
|
+
const timeoutFromEnv = Number(get("HTTP_TIMEOUT_MS") ?? 20000);
|
|
77
|
+
const timeoutMs = typeof input.timeoutMs === "number" && Number.isFinite(input.timeoutMs)
|
|
78
|
+
? input.timeoutMs
|
|
79
|
+
: timeoutFromEnv;
|
|
80
|
+
return {
|
|
81
|
+
sfr: input.sfrToken
|
|
82
|
+
? {
|
|
83
|
+
baseUrl: (get("SFR_BASE_URL") ?? "https://api.sfranalytics.com").replace(/\/$/, ""),
|
|
84
|
+
apiToken: input.sfrToken,
|
|
85
|
+
apiTokenHeader: get("SFR_API_TOKEN_HEADER") ?? "X-API-TOKEN",
|
|
86
|
+
}
|
|
87
|
+
: null,
|
|
88
|
+
plr: input.plrToken
|
|
89
|
+
? {
|
|
90
|
+
baseUrl: (get("PLR_BASE_URL") ?? "https://radar-api.sfranalytics.com").replace(/\/$/, ""),
|
|
91
|
+
apiToken: input.plrToken,
|
|
92
|
+
apiTokenHeader: get("PLR_API_TOKEN_HEADER") ?? "X-API-Key",
|
|
93
|
+
}
|
|
94
|
+
: null,
|
|
95
|
+
snowflake: null,
|
|
96
|
+
http: {
|
|
97
|
+
timeoutMs,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { configFromHeaders } from "./config.js";
|
|
6
|
+
import { createServer } from "./server.js";
|
|
7
|
+
import { ApiError, HttpClient } from "./services/httpClient.js";
|
|
8
|
+
import { VERSION } from "./version.js";
|
|
9
|
+
function env(name) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
return value && value.trim().length > 0 ? value.trim() : undefined;
|
|
12
|
+
}
|
|
13
|
+
function parseCsv(input) {
|
|
14
|
+
if (!input)
|
|
15
|
+
return [];
|
|
16
|
+
return input
|
|
17
|
+
.split(",")
|
|
18
|
+
.map((part) => part.trim())
|
|
19
|
+
.filter((part) => part.length > 0);
|
|
20
|
+
}
|
|
21
|
+
function parseBoolean(input) {
|
|
22
|
+
if (!input)
|
|
23
|
+
return false;
|
|
24
|
+
const normalized = input.trim().toLowerCase();
|
|
25
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
26
|
+
}
|
|
27
|
+
function headerValue(req, name) {
|
|
28
|
+
const value = req.get(name);
|
|
29
|
+
if (!value)
|
|
30
|
+
return undefined;
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
33
|
+
}
|
|
34
|
+
function jsonRpcError(code, message, id = null) {
|
|
35
|
+
return {
|
|
36
|
+
jsonrpc: "2.0",
|
|
37
|
+
error: { code, message },
|
|
38
|
+
id,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function safeErrorMessage(error) {
|
|
42
|
+
if (error instanceof Error && error.message)
|
|
43
|
+
return error.message;
|
|
44
|
+
return "unknown error";
|
|
45
|
+
}
|
|
46
|
+
const host = env("MCP_BIND_HOST") ?? env("HOST") ?? "127.0.0.1";
|
|
47
|
+
const parsedPort = Number.parseInt(env("PORT") ?? "3000", 10);
|
|
48
|
+
const port = Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : 3000;
|
|
49
|
+
const allowedHosts = parseCsv(env("MCP_ALLOWED_HOSTS"));
|
|
50
|
+
const allowedOrigins = parseCsv(env("MCP_ALLOWED_ORIGINS"));
|
|
51
|
+
const allowAnyOrigin = allowedOrigins.includes("*");
|
|
52
|
+
const allowedOriginSet = new Set(allowedOrigins.filter((origin) => origin !== "*"));
|
|
53
|
+
const strictTokenValidation = parseBoolean(env("MCP_STRICT_TOKEN_VALIDATION"));
|
|
54
|
+
const app = createMcpExpressApp({
|
|
55
|
+
host,
|
|
56
|
+
allowedHosts: allowedHosts.length > 0 ? allowedHosts : undefined,
|
|
57
|
+
});
|
|
58
|
+
function isOriginAllowed(origin) {
|
|
59
|
+
return allowAnyOrigin || allowedOriginSet.has(origin);
|
|
60
|
+
}
|
|
61
|
+
function validateOrigin(req, res, next) {
|
|
62
|
+
const origin = headerValue(req, "Origin");
|
|
63
|
+
if (!origin || isOriginAllowed(origin)) {
|
|
64
|
+
next();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
res.status(403).json(jsonRpcError(-32001, "Origin not allowed"));
|
|
68
|
+
}
|
|
69
|
+
const corsOptions = {
|
|
70
|
+
origin(origin, callback) {
|
|
71
|
+
if (!origin || isOriginAllowed(origin)) {
|
|
72
|
+
callback(null, true);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
callback(null, false);
|
|
76
|
+
},
|
|
77
|
+
methods: ["POST", "GET", "DELETE", "OPTIONS"],
|
|
78
|
+
allowedHeaders: [
|
|
79
|
+
"Content-Type",
|
|
80
|
+
"Accept",
|
|
81
|
+
"Mcp-Protocol-Version",
|
|
82
|
+
"Mcp-Session-Id",
|
|
83
|
+
"SFR-Api-Token",
|
|
84
|
+
"PLR-Api-Token",
|
|
85
|
+
],
|
|
86
|
+
exposedHeaders: [
|
|
87
|
+
"Mcp-Session-Id",
|
|
88
|
+
"Mcp-Protocol-Version",
|
|
89
|
+
"mcp-session-id",
|
|
90
|
+
"mcp-protocol-version",
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
// Security + protocol hygiene
|
|
94
|
+
app.use("/mcp", validateOrigin);
|
|
95
|
+
app.use(cors(corsOptions));
|
|
96
|
+
app.use((_req, res, next) => {
|
|
97
|
+
res.setHeader("Cache-Control", "no-store");
|
|
98
|
+
next();
|
|
99
|
+
});
|
|
100
|
+
async function quickTokenCheck(config) {
|
|
101
|
+
const checks = [];
|
|
102
|
+
if (config.sfr) {
|
|
103
|
+
const sfrHttp = new HttpClient({
|
|
104
|
+
baseUrl: config.sfr.baseUrl,
|
|
105
|
+
defaultHeaders: { [config.sfr.apiTokenHeader]: config.sfr.apiToken },
|
|
106
|
+
timeoutMs: config.http.timeoutMs,
|
|
107
|
+
});
|
|
108
|
+
checks.push((async () => {
|
|
109
|
+
try {
|
|
110
|
+
const probeAddress = env("MCP_SFR_TOKEN_PROBE_ADDRESS") ?? "123 Main St, Phoenix, AZ 85004";
|
|
111
|
+
await sfrHttp.request("GET", "/properties/by-address", { query: { address: probeAddress } });
|
|
112
|
+
return "valid";
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error instanceof ApiError && (error.status === 401 || error.status === 403))
|
|
116
|
+
return "invalid";
|
|
117
|
+
if (error instanceof ApiError && (error.status === 408 || error.status === 429 || error.status >= 500)) {
|
|
118
|
+
return "unavailable";
|
|
119
|
+
}
|
|
120
|
+
if (error instanceof ApiError) {
|
|
121
|
+
// Non-auth client errors (400, 404, 422) mean the server authenticated the request
|
|
122
|
+
// but the probe query was invalid — token is valid, probe params are just wrong
|
|
123
|
+
if (error.status !== 200) {
|
|
124
|
+
console.error(`SFR token probe returned unexpected status ${error.status}`);
|
|
125
|
+
}
|
|
126
|
+
return "valid";
|
|
127
|
+
}
|
|
128
|
+
return "unavailable";
|
|
129
|
+
}
|
|
130
|
+
})());
|
|
131
|
+
}
|
|
132
|
+
if (config.plr) {
|
|
133
|
+
const plrHttp = new HttpClient({
|
|
134
|
+
baseUrl: config.plr.baseUrl,
|
|
135
|
+
defaultHeaders: { [config.plr.apiTokenHeader]: config.plr.apiToken },
|
|
136
|
+
timeoutMs: config.http.timeoutMs,
|
|
137
|
+
});
|
|
138
|
+
checks.push((async () => {
|
|
139
|
+
try {
|
|
140
|
+
await plrHttp.request("GET", "/api/v1/analytics/portfolio/portfolio_summary/", {
|
|
141
|
+
query: { start_date: "2024-01-01", end_date: "2024-01-31" },
|
|
142
|
+
});
|
|
143
|
+
return "valid";
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof ApiError && (error.status === 401 || error.status === 403))
|
|
147
|
+
return "invalid";
|
|
148
|
+
if (error instanceof ApiError && (error.status === 408 || error.status === 429 || error.status >= 500)) {
|
|
149
|
+
return "unavailable";
|
|
150
|
+
}
|
|
151
|
+
if (error instanceof ApiError) {
|
|
152
|
+
if (error.status !== 200) {
|
|
153
|
+
console.error(`PLR token probe returned unexpected status ${error.status}`);
|
|
154
|
+
}
|
|
155
|
+
return "valid";
|
|
156
|
+
}
|
|
157
|
+
return "unavailable";
|
|
158
|
+
}
|
|
159
|
+
})());
|
|
160
|
+
}
|
|
161
|
+
if (checks.length === 0)
|
|
162
|
+
return "invalid";
|
|
163
|
+
const results = await Promise.all(checks);
|
|
164
|
+
if (results.includes("invalid"))
|
|
165
|
+
return "invalid";
|
|
166
|
+
if (results.includes("unavailable"))
|
|
167
|
+
return "unavailable";
|
|
168
|
+
return "valid";
|
|
169
|
+
}
|
|
170
|
+
app.post("/mcp", async (req, res) => {
|
|
171
|
+
const sfrToken = headerValue(req, "SFR-Api-Token");
|
|
172
|
+
const plrToken = headerValue(req, "PLR-Api-Token");
|
|
173
|
+
if (!sfrToken && !plrToken) {
|
|
174
|
+
res
|
|
175
|
+
.status(401)
|
|
176
|
+
.json(jsonRpcError(-32001, "Missing API token. Provide SFR-Api-Token and/or PLR-Api-Token."));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const config = configFromHeaders({ sfrToken, plrToken });
|
|
180
|
+
if (strictTokenValidation) {
|
|
181
|
+
try {
|
|
182
|
+
const validationResult = await quickTokenCheck(config);
|
|
183
|
+
if (validationResult === "invalid") {
|
|
184
|
+
res.status(401).json(jsonRpcError(-32001, "Invalid API token."));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (validationResult === "unavailable") {
|
|
188
|
+
res.status(503).json(jsonRpcError(-32603, "Token validation unavailable. Try again later."));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error(`Token validation error: ${safeErrorMessage(error)}`);
|
|
194
|
+
res.status(503).json(jsonRpcError(-32603, "Token validation unavailable. Try again later."));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const server = createServer(config);
|
|
199
|
+
const transport = new StreamableHTTPServerTransport({
|
|
200
|
+
sessionIdGenerator: undefined,
|
|
201
|
+
enableJsonResponse: true,
|
|
202
|
+
});
|
|
203
|
+
const cleanup = () => {
|
|
204
|
+
res.off("close", cleanup);
|
|
205
|
+
void transport.close().catch(() => { });
|
|
206
|
+
void server.close().catch(() => { });
|
|
207
|
+
};
|
|
208
|
+
res.on("close", cleanup);
|
|
209
|
+
try {
|
|
210
|
+
await server.connect(transport);
|
|
211
|
+
await transport.handleRequest(req, res, req.body);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error(`Error handling MCP request: ${safeErrorMessage(error)}`);
|
|
215
|
+
if (!res.headersSent) {
|
|
216
|
+
res.status(500).json(jsonRpcError(-32603, "Internal server error"));
|
|
217
|
+
}
|
|
218
|
+
cleanup();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
const methodNotAllowed = (_req, res) => {
|
|
222
|
+
res.status(405).json(jsonRpcError(-32000, "Method not allowed."));
|
|
223
|
+
};
|
|
224
|
+
app.get("/mcp", methodNotAllowed);
|
|
225
|
+
app.delete("/mcp", methodNotAllowed);
|
|
226
|
+
app.options("/mcp", cors(corsOptions));
|
|
227
|
+
app.get("/health", (_req, res) => {
|
|
228
|
+
res.status(200).json({ ok: true, version: VERSION });
|
|
229
|
+
});
|
|
230
|
+
const httpServer = app.listen(port, host, () => {
|
|
231
|
+
if (allowedOriginSet.size === 0 && !allowAnyOrigin) {
|
|
232
|
+
console.error("MCP_ALLOWED_ORIGINS not set. Requests with an Origin header will be rejected.");
|
|
233
|
+
}
|
|
234
|
+
console.error(`@sfranalytics/mcp-http v${VERSION} listening on http://${host}:${port}/mcp`);
|
|
235
|
+
});
|
|
236
|
+
httpServer.on("error", (error) => {
|
|
237
|
+
console.error("Failed to start HTTP server:", error);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
|
240
|
+
function shutdown(signal) {
|
|
241
|
+
console.error(`Received ${signal}; shutting down HTTP server...`);
|
|
242
|
+
httpServer.close((error) => {
|
|
243
|
+
if (error) {
|
|
244
|
+
console.error("Error during shutdown:", error);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
process.exit(0);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
251
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
252
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
import { VERSION } from "./version.js";
|
|
6
|
+
import { disconnectSnowflake, isSnowflakeConfigured } from "./services/snowflake.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const sfrCount = config.sfr ? 21 : 0;
|
|
10
|
+
const plrCount = config.plr ? 15 : 0;
|
|
11
|
+
const sfCount = config.snowflake ? 10 : 0;
|
|
12
|
+
const parts = [];
|
|
13
|
+
if (config.sfr)
|
|
14
|
+
parts.push(`SFR (${sfrCount} tools)`);
|
|
15
|
+
if (config.plr)
|
|
16
|
+
parts.push(`PLR (${plrCount} tools)`);
|
|
17
|
+
if (config.snowflake)
|
|
18
|
+
parts.push(`Snowflake (${sfCount} tools)`);
|
|
19
|
+
const server = createServer(config);
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
// stdout is reserved for MCP messages — log to stderr only
|
|
23
|
+
console.error(`@sfranalytics/mcp v${VERSION} — ${parts.join(" + ")} + health`);
|
|
24
|
+
}
|
|
25
|
+
// Graceful shutdown
|
|
26
|
+
async function cleanup() {
|
|
27
|
+
if (isSnowflakeConfigured()) {
|
|
28
|
+
try {
|
|
29
|
+
await disconnectSnowflake();
|
|
30
|
+
}
|
|
31
|
+
catch { /* ignore */ }
|
|
32
|
+
}
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
process.on("SIGINT", cleanup);
|
|
36
|
+
process.on("SIGTERM", cleanup);
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
console.error("Fatal:", err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,QAAQ,SAAS,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,QAAQ,SAAS,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,OAAO,SAAS,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,2DAA2D;IAC3D,OAAO,CAAC,KAAK,CAAC,sBAAsB,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AACjF,CAAC;AAED,oBAAoB;AACpB,KAAK,UAAU,OAAO;IACpB,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC;YAAC,MAAM,mBAAmB,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAC9B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAE/B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED