hotelzero 1.7.0 → 1.9.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/README.md +32 -0
- package/dist/browser.d.ts +15 -1
- package/dist/browser.js +28 -3
- package/dist/index.js +38 -3
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -25,6 +25,30 @@ Or run directly with npx:
|
|
|
25
25
|
npx hotelzero
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## Proxy Support
|
|
29
|
+
|
|
30
|
+
For heavy usage or to avoid IP blocks, you can configure a proxy server via the `HOTELZERO_PROXY` environment variable:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# HTTP proxy
|
|
34
|
+
HOTELZERO_PROXY=http://proxy.example.com:8080 npx hotelzero
|
|
35
|
+
|
|
36
|
+
# HTTP proxy with authentication
|
|
37
|
+
HOTELZERO_PROXY=http://user:pass@proxy.example.com:8080 npx hotelzero
|
|
38
|
+
|
|
39
|
+
# SOCKS5 proxy
|
|
40
|
+
HOTELZERO_PROXY=socks5://proxy.example.com:1080 npx hotelzero
|
|
41
|
+
|
|
42
|
+
# SOCKS5 proxy with authentication
|
|
43
|
+
HOTELZERO_PROXY=socks5://user:pass@proxy.example.com:1080 npx hotelzero
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When a proxy is configured, you'll see confirmation in the startup logs:
|
|
47
|
+
```
|
|
48
|
+
Proxy enabled: http://proxy.example.com:8080
|
|
49
|
+
HotelZero v1.8.0 running on stdio
|
|
50
|
+
```
|
|
51
|
+
|
|
28
52
|
## Quick Start
|
|
29
53
|
|
|
30
54
|
### Run as MCP Server
|
|
@@ -516,6 +540,14 @@ Run `npx playwright install chromium` to install the browser.
|
|
|
516
540
|
|
|
517
541
|
- Wait a few minutes before retrying
|
|
518
542
|
- The server uses anti-detection measures, but excessive requests may trigger blocks
|
|
543
|
+
- Consider using a proxy server (see [Proxy Support](#proxy-support))
|
|
544
|
+
|
|
545
|
+
### Proxy not working
|
|
546
|
+
|
|
547
|
+
- Verify the proxy server is running and accessible
|
|
548
|
+
- Check credentials if using authentication
|
|
549
|
+
- Ensure the proxy supports HTTPS connections
|
|
550
|
+
- Try a different proxy or test without proxy first
|
|
519
551
|
|
|
520
552
|
---
|
|
521
553
|
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface ProxyConfig {
|
|
2
|
+
server: string;
|
|
3
|
+
username?: string;
|
|
4
|
+
password?: string;
|
|
5
|
+
}
|
|
1
6
|
export declare class HotelSearchError extends Error {
|
|
2
7
|
code: string;
|
|
3
8
|
retryable: boolean;
|
|
@@ -225,7 +230,16 @@ export declare class HotelBrowser {
|
|
|
225
230
|
private page;
|
|
226
231
|
private lastRequestTime;
|
|
227
232
|
private minRequestIntervalMs;
|
|
228
|
-
|
|
233
|
+
private proxyConfig;
|
|
234
|
+
init(headless?: boolean, proxy?: ProxyConfig): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Check if a proxy is configured
|
|
237
|
+
*/
|
|
238
|
+
hasProxy(): boolean;
|
|
239
|
+
/**
|
|
240
|
+
* Get the current proxy server (without credentials)
|
|
241
|
+
*/
|
|
242
|
+
getProxyServer(): string | null;
|
|
229
243
|
close(): Promise<void>;
|
|
230
244
|
private buildBookingUrl;
|
|
231
245
|
private enforceRateLimit;
|
package/dist/browser.js
CHANGED
|
@@ -232,17 +232,42 @@ export class HotelBrowser {
|
|
|
232
232
|
page = null;
|
|
233
233
|
lastRequestTime = 0;
|
|
234
234
|
minRequestIntervalMs = 2000; // Minimum 2 seconds between requests
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
proxyConfig = null;
|
|
236
|
+
async init(headless = true, proxy) {
|
|
237
|
+
// Store proxy config for reference
|
|
238
|
+
this.proxyConfig = proxy || null;
|
|
239
|
+
// Build launch options
|
|
240
|
+
const launchOptions = {
|
|
237
241
|
headless,
|
|
238
242
|
args: ["--disable-blink-features=AutomationControlled"],
|
|
239
|
-
}
|
|
243
|
+
};
|
|
244
|
+
// Add proxy to launch options if provided
|
|
245
|
+
if (proxy) {
|
|
246
|
+
launchOptions.proxy = {
|
|
247
|
+
server: proxy.server,
|
|
248
|
+
username: proxy.username,
|
|
249
|
+
password: proxy.password,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
this.browser = await chromium.launch(launchOptions);
|
|
240
253
|
const context = await this.browser.newContext({
|
|
241
254
|
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
242
255
|
viewport: { width: 1280, height: 900 },
|
|
243
256
|
});
|
|
244
257
|
this.page = await context.newPage();
|
|
245
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if a proxy is configured
|
|
261
|
+
*/
|
|
262
|
+
hasProxy() {
|
|
263
|
+
return this.proxyConfig !== null;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get the current proxy server (without credentials)
|
|
267
|
+
*/
|
|
268
|
+
getProxyServer() {
|
|
269
|
+
return this.proxyConfig?.server || null;
|
|
270
|
+
}
|
|
246
271
|
async close() {
|
|
247
272
|
if (this.browser) {
|
|
248
273
|
await this.browser.close();
|
package/dist/index.js
CHANGED
|
@@ -149,10 +149,45 @@ const GetPriceCalendarSchema = z.object({
|
|
|
149
149
|
});
|
|
150
150
|
// Global browser instance (reuse for efficiency)
|
|
151
151
|
let browser = null;
|
|
152
|
+
/**
|
|
153
|
+
* Parse proxy configuration from environment variable
|
|
154
|
+
* Supports formats:
|
|
155
|
+
* - http://proxy.example.com:8080
|
|
156
|
+
* - http://user:pass@proxy.example.com:8080
|
|
157
|
+
* - socks5://proxy.example.com:1080
|
|
158
|
+
* - socks5://user:pass@proxy.example.com:1080
|
|
159
|
+
*/
|
|
160
|
+
function parseProxyFromEnv() {
|
|
161
|
+
const proxyUrl = process.env.HOTELZERO_PROXY;
|
|
162
|
+
if (!proxyUrl)
|
|
163
|
+
return undefined;
|
|
164
|
+
try {
|
|
165
|
+
const url = new URL(proxyUrl);
|
|
166
|
+
const config = {
|
|
167
|
+
server: `${url.protocol}//${url.host}`,
|
|
168
|
+
};
|
|
169
|
+
if (url.username) {
|
|
170
|
+
config.username = decodeURIComponent(url.username);
|
|
171
|
+
}
|
|
172
|
+
if (url.password) {
|
|
173
|
+
config.password = decodeURIComponent(url.password);
|
|
174
|
+
}
|
|
175
|
+
return config;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error(`Invalid HOTELZERO_PROXY format: ${proxyUrl}`);
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
152
182
|
async function getBrowser() {
|
|
153
183
|
if (!browser) {
|
|
154
184
|
browser = new HotelBrowser();
|
|
155
|
-
|
|
185
|
+
const proxyConfig = parseProxyFromEnv();
|
|
186
|
+
await browser.init(true, proxyConfig);
|
|
187
|
+
// Log proxy status (without credentials)
|
|
188
|
+
if (browser.hasProxy()) {
|
|
189
|
+
console.error(`Proxy enabled: ${browser.getProxyServer()}`);
|
|
190
|
+
}
|
|
156
191
|
}
|
|
157
192
|
return browser;
|
|
158
193
|
}
|
|
@@ -511,7 +546,7 @@ function formatPriceCalendarResult(result) {
|
|
|
511
546
|
// Create MCP server
|
|
512
547
|
const server = new Server({
|
|
513
548
|
name: "hotelzero",
|
|
514
|
-
version: "1.
|
|
549
|
+
version: "1.8.0",
|
|
515
550
|
}, {
|
|
516
551
|
capabilities: {
|
|
517
552
|
tools: {},
|
|
@@ -1012,7 +1047,7 @@ process.on("SIGTERM", async () => {
|
|
|
1012
1047
|
async function main() {
|
|
1013
1048
|
const transport = new StdioServerTransport();
|
|
1014
1049
|
await server.connect(transport);
|
|
1015
|
-
console.error("HotelZero v1.
|
|
1050
|
+
console.error("HotelZero v1.8.0 running on stdio");
|
|
1016
1051
|
}
|
|
1017
1052
|
main().catch((error) => {
|
|
1018
1053
|
console.error("Fatal error:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hotelzero",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "MCP server for searching hotels on Booking.com with 80+ filters",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,7 +15,14 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"start": "node dist/index.js",
|
|
17
17
|
"dev": "tsx src/index.ts",
|
|
18
|
-
"test": "
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:ui": "vitest --ui",
|
|
21
|
+
"test:selectors": "vitest run tests/selectors.test.ts",
|
|
22
|
+
"test:search": "vitest run tests/search.test.ts",
|
|
23
|
+
"test:details": "vitest run tests/details.test.ts",
|
|
24
|
+
"test:reviews": "vitest run tests/reviews.test.ts",
|
|
25
|
+
"test:calendar": "vitest run tests/price-calendar.test.ts",
|
|
19
26
|
"prepublishOnly": "npm run build"
|
|
20
27
|
},
|
|
21
28
|
"keywords": [
|
|
@@ -48,7 +55,9 @@
|
|
|
48
55
|
},
|
|
49
56
|
"devDependencies": {
|
|
50
57
|
"@types/node": "^25.2.3",
|
|
58
|
+
"@vitest/ui": "^4.0.18",
|
|
51
59
|
"tsx": "^4.21.0",
|
|
52
|
-
"typescript": "^5.9.3"
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vitest": "^4.0.18"
|
|
53
62
|
}
|
|
54
63
|
}
|