hotelzero 1.2.0 → 1.3.1

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/browser.d.ts CHANGED
@@ -22,6 +22,7 @@ export interface HotelSearchParams {
22
22
  rooms: number;
23
23
  currency?: string;
24
24
  sortBy?: "popularity" | "price_lowest" | "price_highest" | "rating" | "distance";
25
+ limit?: number;
25
26
  }
26
27
  export interface HotelFilters {
27
28
  minRating?: number;
package/dist/browser.js CHANGED
@@ -576,10 +576,15 @@ export class HotelBrowser {
576
576
  await this.checkForNoResults();
577
577
  // Close any popups/modals
578
578
  await this.dismissPopups();
579
- // Scroll to load more results
580
- await this.scrollToLoadMore();
579
+ // Scroll to load more results (pass limit to control how many to load)
580
+ const targetResults = params.limit || 25;
581
+ await this.scrollToLoadMore(targetResults);
581
582
  // Extract detailed hotel info
582
- const hotels = await this.extractHotelDetails();
583
+ let hotels = await this.extractHotelDetails();
584
+ // Apply limit to cap results
585
+ if (params.limit && params.limit > 0) {
586
+ hotels = hotels.slice(0, params.limit);
587
+ }
583
588
  // Apply client-side filtering and scoring if we have preferences
584
589
  if (filters) {
585
590
  return this.scoreAndFilterHotels(hotels, filters);
@@ -613,20 +618,45 @@ export class HotelBrowser {
613
618
  }
614
619
  }
615
620
  }
616
- async scrollToLoadMore() {
621
+ async scrollToLoadMore(targetResults = 25) {
617
622
  if (!this.page)
618
623
  return;
619
- // Scroll down a few times to load more results
620
- for (let i = 0; i < 3; i++) {
621
- await this.page.evaluate(() => {
622
- window.scrollBy(0, window.innerHeight);
623
- });
624
+ // If we only need 25 or fewer, minimal scrolling
625
+ if (targetResults <= 25) {
626
+ // Just one scroll to ensure initial results are loaded
627
+ await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
624
628
  await this.page.waitForTimeout(1000);
629
+ await this.page.evaluate(() => window.scrollTo(0, 0));
630
+ return;
631
+ }
632
+ // For larger limits, we need to scroll and click "Load more" multiple times
633
+ // Booking.com loads ~25 results initially, then ~25 more per "Load more" click
634
+ const clicksNeeded = Math.ceil((targetResults - 25) / 25);
635
+ const maxClicks = Math.min(clicksNeeded, 4); // Cap at 4 clicks (~125 results max)
636
+ for (let i = 0; i < maxClicks; i++) {
637
+ // Scroll to bottom to trigger lazy loading and find "Load more" button
638
+ await this.page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
639
+ await this.page.waitForTimeout(1500);
640
+ // Try to click "Load more" button
641
+ try {
642
+ const loadMoreBtn = await this.page.$('button[data-testid="load-more-results"]');
643
+ if (loadMoreBtn) {
644
+ await loadMoreBtn.click();
645
+ await this.page.waitForTimeout(2000); // Wait for results to load
646
+ }
647
+ else {
648
+ // No more "Load more" button, we've loaded all available results
649
+ break;
650
+ }
651
+ }
652
+ catch {
653
+ // Button not found or click failed
654
+ break;
655
+ }
625
656
  }
626
657
  // Scroll back to top
627
- await this.page.evaluate(() => {
628
- window.scrollTo(0, 0);
629
- });
658
+ await this.page.evaluate(() => window.scrollTo(0, 0));
659
+ await this.page.waitForTimeout(500);
630
660
  }
631
661
  async extractHotelDetails() {
632
662
  if (!this.page)
package/dist/index.js CHANGED
@@ -40,6 +40,8 @@ const FindHotelsSchema = z.object({
40
40
  // Currency & Sorting
41
41
  currency: z.string().optional().describe("Currency code (USD, EUR, GBP, JPY, etc.)"),
42
42
  sortBy: z.enum(["popularity", "price_lowest", "price_highest", "rating", "distance"]).optional().describe("Sort results by"),
43
+ // Pagination
44
+ limit: z.number().min(1).max(100).optional().describe("Maximum number of results to return (default: 25, max: 100)"),
43
45
  // Rating & Price
44
46
  minRating: z.number().optional().describe("Minimum rating (6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful)"),
45
47
  minPrice: z.number().optional().describe("Minimum price per night"),
@@ -165,7 +167,7 @@ function formatHotelResult(hotel, index) {
165
167
  // Create MCP server
166
168
  const server = new Server({
167
169
  name: "hotelzero",
168
- version: "1.2.0",
170
+ version: "1.3.1",
169
171
  }, {
170
172
  capabilities: {
171
173
  tools: {},
@@ -192,6 +194,8 @@ const findHotelsInputSchema = {
192
194
  description: "Sort results by",
193
195
  enum: ["popularity", "price_lowest", "price_highest", "rating", "distance"]
194
196
  },
197
+ // Pagination
198
+ limit: { type: "number", description: "Maximum results to return (default: 25, max: 100)", default: 25 },
195
199
  // Property Type
196
200
  propertyType: {
197
201
  type: "string",
@@ -353,11 +357,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
353
357
  rooms: parsed.rooms,
354
358
  currency: parsed.currency,
355
359
  sortBy: parsed.sortBy,
360
+ limit: parsed.limit,
356
361
  };
357
362
  // Build filters object from all parsed parameters
358
363
  const filters = {};
359
- // Copy all filter properties
360
- const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy'].includes(k));
364
+ // Copy all filter properties (exclude search params)
365
+ const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy', 'limit'].includes(k));
361
366
  for (const key of filterKeys) {
362
367
  const value = parsed[key];
363
368
  if (value !== undefined && value !== null) {
@@ -429,7 +434,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
429
434
  ? `Filters: ${activeFilters.join(", ")}\n\n`
430
435
  : "\n";
431
436
  const hotelList = results
432
- .slice(0, 15) // Top 15 results
433
437
  .map((h, i) => formatHotelResult(h, i))
434
438
  .join("\n\n");
435
439
  return {
@@ -544,7 +548,7 @@ process.on("SIGTERM", async () => {
544
548
  async function main() {
545
549
  const transport = new StdioServerTransport();
546
550
  await server.connect(transport);
547
- console.error("HotelZero v1.2.0 running on stdio");
551
+ console.error("HotelZero v1.3.1 running on stdio");
548
552
  }
549
553
  main().catch((error) => {
550
554
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hotelzero",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
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",