hotelzero 1.2.0 → 1.3.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/dist/browser.d.ts CHANGED
@@ -22,6 +22,8 @@ export interface HotelSearchParams {
22
22
  rooms: number;
23
23
  currency?: string;
24
24
  sortBy?: "popularity" | "price_lowest" | "price_highest" | "rating" | "distance";
25
+ limit?: number;
26
+ offset?: number;
25
27
  }
26
28
  export interface HotelFilters {
27
29
  minRating?: number;
package/dist/browser.js CHANGED
@@ -251,7 +251,7 @@ export class HotelBrowser {
251
251
  }
252
252
  }
253
253
  buildBookingUrl(params, filters) {
254
- const { destination, checkIn, checkOut, guests, rooms, currency, sortBy } = params;
254
+ const { destination, checkIn, checkOut, guests, rooms, currency, sortBy, offset } = params;
255
255
  const url = new URL("https://www.booking.com/searchresults.html");
256
256
  url.searchParams.set("ss", destination);
257
257
  url.searchParams.set("checkin", checkIn);
@@ -259,6 +259,10 @@ export class HotelBrowser {
259
259
  url.searchParams.set("group_adults", guests.toString());
260
260
  url.searchParams.set("no_rooms", rooms.toString());
261
261
  url.searchParams.set("selected_currency", currency || "USD");
262
+ // Pagination offset
263
+ if (offset && offset > 0) {
264
+ url.searchParams.set("offset", offset.toString());
265
+ }
262
266
  // Sort order
263
267
  if (sortBy) {
264
268
  const sortMap = {
@@ -576,10 +580,15 @@ export class HotelBrowser {
576
580
  await this.checkForNoResults();
577
581
  // Close any popups/modals
578
582
  await this.dismissPopups();
579
- // Scroll to load more results
580
- await this.scrollToLoadMore();
583
+ // Scroll to load more results (pass limit to control how many to load)
584
+ const targetResults = params.limit || 25;
585
+ await this.scrollToLoadMore(targetResults);
581
586
  // Extract detailed hotel info
582
- const hotels = await this.extractHotelDetails();
587
+ let hotels = await this.extractHotelDetails();
588
+ // Apply limit to cap results
589
+ if (params.limit && params.limit > 0) {
590
+ hotels = hotels.slice(0, params.limit);
591
+ }
583
592
  // Apply client-side filtering and scoring if we have preferences
584
593
  if (filters) {
585
594
  return this.scoreAndFilterHotels(hotels, filters);
@@ -613,15 +622,31 @@ export class HotelBrowser {
613
622
  }
614
623
  }
615
624
  }
616
- async scrollToLoadMore() {
625
+ async scrollToLoadMore(targetResults = 25) {
617
626
  if (!this.page)
618
627
  return;
619
- // Scroll down a few times to load more results
620
- for (let i = 0; i < 3; i++) {
628
+ // Calculate how many scroll iterations needed
629
+ // Each scroll typically loads ~15-25 more results
630
+ // We start with ~25, so to get to targetResults we need (targetResults - 25) / 20 more scrolls
631
+ const scrollsNeeded = Math.max(1, Math.ceil((targetResults - 25) / 20));
632
+ const maxScrolls = Math.min(scrollsNeeded, 5); // Cap at 5 scrolls to avoid excessive loading
633
+ // Scroll down to load more results
634
+ for (let i = 0; i < maxScrolls; i++) {
621
635
  await this.page.evaluate(() => {
622
636
  window.scrollBy(0, window.innerHeight);
623
637
  });
624
638
  await this.page.waitForTimeout(1000);
639
+ // Check if "Load more" button exists and click it
640
+ try {
641
+ const loadMoreBtn = await this.page.$('button[data-testid="load-more-results"]');
642
+ if (loadMoreBtn) {
643
+ await loadMoreBtn.click();
644
+ await this.page.waitForTimeout(1500);
645
+ }
646
+ }
647
+ catch {
648
+ // Ignore if button not found
649
+ }
625
650
  }
626
651
  // Scroll back to top
627
652
  await this.page.evaluate(() => {
package/dist/index.js CHANGED
@@ -40,6 +40,9 @@ 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)"),
45
+ offset: z.number().min(0).optional().describe("Number of results to skip for pagination (default: 0)"),
43
46
  // Rating & Price
44
47
  minRating: z.number().optional().describe("Minimum rating (6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful)"),
45
48
  minPrice: z.number().optional().describe("Minimum price per night"),
@@ -165,7 +168,7 @@ function formatHotelResult(hotel, index) {
165
168
  // Create MCP server
166
169
  const server = new Server({
167
170
  name: "hotelzero",
168
- version: "1.2.0",
171
+ version: "1.3.0",
169
172
  }, {
170
173
  capabilities: {
171
174
  tools: {},
@@ -192,6 +195,9 @@ const findHotelsInputSchema = {
192
195
  description: "Sort results by",
193
196
  enum: ["popularity", "price_lowest", "price_highest", "rating", "distance"]
194
197
  },
198
+ // Pagination
199
+ limit: { type: "number", description: "Maximum results to return (default: 25, max: 100)", default: 25 },
200
+ offset: { type: "number", description: "Number of results to skip for pagination", default: 0 },
195
201
  // Property Type
196
202
  propertyType: {
197
203
  type: "string",
@@ -353,11 +359,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
353
359
  rooms: parsed.rooms,
354
360
  currency: parsed.currency,
355
361
  sortBy: parsed.sortBy,
362
+ limit: parsed.limit,
363
+ offset: parsed.offset,
356
364
  };
357
365
  // Build filters object from all parsed parameters
358
366
  const filters = {};
359
- // Copy all filter properties
360
- const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy'].includes(k));
367
+ // Copy all filter properties (exclude search params)
368
+ const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy', 'limit', 'offset'].includes(k));
361
369
  for (const key of filterKeys) {
362
370
  const value = parsed[key];
363
371
  if (value !== undefined && value !== null) {
@@ -428,15 +436,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
428
436
  const filtersLine = activeFilters.length > 0
429
437
  ? `Filters: ${activeFilters.join(", ")}\n\n`
430
438
  : "\n";
439
+ // Pagination info
440
+ const offset = parsed.offset || 0;
441
+ const displayLimit = parsed.limit || 25;
442
+ const paginationLine = offset > 0
443
+ ? `Showing results ${offset + 1}-${offset + results.length} of available hotels\n\n`
444
+ : "";
431
445
  const hotelList = results
432
- .slice(0, 15) // Top 15 results
433
- .map((h, i) => formatHotelResult(h, i))
446
+ .map((h, i) => formatHotelResult(h, i + offset))
434
447
  .join("\n\n");
435
448
  return {
436
449
  content: [
437
450
  {
438
451
  type: "text",
439
- text: header + filtersLine + hotelList,
452
+ text: header + filtersLine + paginationLine + hotelList,
440
453
  },
441
454
  ],
442
455
  };
@@ -544,7 +557,7 @@ process.on("SIGTERM", async () => {
544
557
  async function main() {
545
558
  const transport = new StdioServerTransport();
546
559
  await server.connect(transport);
547
- console.error("HotelZero v1.2.0 running on stdio");
560
+ console.error("HotelZero v1.3.0 running on stdio");
548
561
  }
549
562
  main().catch((error) => {
550
563
  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.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",