hotelzero 1.0.0 → 1.1.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 CHANGED
@@ -100,13 +100,16 @@ Get detailed information about a specific hotel including full amenity list, des
100
100
  | `checkOut` | string | Yes | Check-out date (YYYY-MM-DD) |
101
101
  | `guests` | number | No | Number of guests (default: 2) |
102
102
  | `rooms` | number | No | Number of rooms (default: 1) |
103
+ | `currency` | string | No | Currency code (USD, EUR, GBP, JPY, etc.) Default: USD |
104
+ | `sortBy` | enum | No | Sort results: `popularity`, `price_lowest`, `price_highest`, `rating`, `distance` |
103
105
 
104
106
  ### Rating & Price
105
107
 
106
108
  | Filter | Type | Description |
107
109
  |--------|------|-------------|
108
110
  | `minRating` | number | Minimum review score: 6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful |
109
- | `maxPrice` | number | Maximum price per night in USD (client-side filter) |
111
+ | `minPrice` | number | Minimum price per night |
112
+ | `maxPrice` | number | Maximum price per night |
110
113
 
111
114
  ### Property Type
112
115
 
package/dist/browser.d.ts CHANGED
@@ -4,9 +4,12 @@ export interface HotelSearchParams {
4
4
  checkOut: string;
5
5
  guests: number;
6
6
  rooms: number;
7
+ currency?: string;
8
+ sortBy?: "popularity" | "price_lowest" | "price_highest" | "rating" | "distance";
7
9
  }
8
10
  export interface HotelFilters {
9
11
  minRating?: number;
12
+ minPrice?: number;
10
13
  maxPrice?: number;
11
14
  propertyType?: "hotel" | "apartment" | "resort" | "villa" | "vacation_home" | "hostel" | "bnb" | "guesthouse" | "homestay" | "motel" | "inn" | "lodge" | "chalet" | "campground" | "glamping" | "boat" | "capsule" | "ryokan" | "riad" | "country_house" | "farm_stay";
12
15
  starRating?: 1 | 2 | 3 | 4 | 5;
@@ -99,6 +102,8 @@ export interface HotelResult {
99
102
  amenities: string[];
100
103
  highlights: string[];
101
104
  link: string;
105
+ thumbnailUrl: string | null;
106
+ availability: string | null;
102
107
  matchScore?: number;
103
108
  matchReasons?: string[];
104
109
  }
package/dist/browser.js CHANGED
@@ -191,14 +191,28 @@ export class HotelBrowser {
191
191
  }
192
192
  }
193
193
  buildBookingUrl(params, filters) {
194
- const { destination, checkIn, checkOut, guests, rooms } = params;
194
+ const { destination, checkIn, checkOut, guests, rooms, currency, sortBy } = params;
195
195
  const url = new URL("https://www.booking.com/searchresults.html");
196
196
  url.searchParams.set("ss", destination);
197
197
  url.searchParams.set("checkin", checkIn);
198
198
  url.searchParams.set("checkout", checkOut);
199
199
  url.searchParams.set("group_adults", guests.toString());
200
200
  url.searchParams.set("no_rooms", rooms.toString());
201
- url.searchParams.set("selected_currency", "USD");
201
+ url.searchParams.set("selected_currency", currency || "USD");
202
+ // Sort order
203
+ if (sortBy) {
204
+ const sortMap = {
205
+ popularity: "popularity",
206
+ price_lowest: "price",
207
+ price_highest: "price",
208
+ rating: "review_score_and_price",
209
+ distance: "distance_from_search",
210
+ };
211
+ url.searchParams.set("order", sortMap[sortBy] || "popularity");
212
+ if (sortBy === "price_highest") {
213
+ url.searchParams.set("sort_order", "desc");
214
+ }
215
+ }
202
216
  if (!filters)
203
217
  return url.toString();
204
218
  const nfltParts = [];
@@ -557,6 +571,24 @@ export class HotelBrowser {
557
571
  highlights.push("Free Cancellation");
558
572
  if (cardText.includes("no prepayment"))
559
573
  highlights.push("No Prepayment");
574
+ // Extract thumbnail image URL
575
+ const imgEl = card.querySelector('img[data-testid="image"]');
576
+ const thumbnailUrl = imgEl?.src || null;
577
+ // Extract availability status (e.g., "Only 2 rooms left", "Last booked 5 minutes ago")
578
+ let availability = null;
579
+ const availabilityPatterns = [
580
+ /only\s*\d+\s*(rooms?|left)/i,
581
+ /last\s*(booked|reserved)\s*\d+\s*(minutes?|hours?)\s*ago/i,
582
+ /in\s*high\s*demand/i,
583
+ /selling\s*fast/i,
584
+ ];
585
+ for (const pattern of availabilityPatterns) {
586
+ const match = cardText.match(pattern);
587
+ if (match) {
588
+ availability = match[0];
589
+ break;
590
+ }
591
+ }
560
592
  results.push({
561
593
  name,
562
594
  price,
@@ -569,6 +601,8 @@ export class HotelBrowser {
569
601
  amenities,
570
602
  highlights,
571
603
  link,
604
+ thumbnailUrl,
605
+ availability,
572
606
  });
573
607
  });
574
608
  return results;
@@ -682,6 +716,9 @@ export class HotelBrowser {
682
716
  if (filters.minRating && hotel.rating && hotel.rating < filters.minRating) {
683
717
  return false;
684
718
  }
719
+ if (filters.minPrice && hotel.price && hotel.price < filters.minPrice) {
720
+ return false;
721
+ }
685
722
  if (filters.maxPrice && hotel.price && hotel.price > filters.maxPrice) {
686
723
  return false;
687
724
  }
package/dist/index.js CHANGED
@@ -37,9 +37,13 @@ const FindHotelsSchema = z.object({
37
37
  checkOut: z.string().describe("Check-out date (YYYY-MM-DD)"),
38
38
  guests: z.number().default(2).describe("Number of guests"),
39
39
  rooms: z.number().default(1).describe("Number of rooms"),
40
+ // Currency & Sorting
41
+ currency: z.string().optional().describe("Currency code (USD, EUR, GBP, JPY, etc.)"),
42
+ sortBy: z.enum(["popularity", "price_lowest", "price_highest", "rating", "distance"]).optional().describe("Sort results by"),
40
43
  // Rating & Price
41
44
  minRating: z.number().optional().describe("Minimum rating (6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful)"),
42
- maxPrice: z.number().optional().describe("Maximum price per night in USD"),
45
+ minPrice: z.number().optional().describe("Minimum price per night"),
46
+ maxPrice: z.number().optional().describe("Maximum price per night"),
43
47
  // Property & Star Rating
44
48
  propertyType: PropertyTypeEnum.optional().describe("Type of property (hotel, resort, apartment, villa, etc.)"),
45
49
  starRating: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5)]).optional().describe("Star rating (1-5)"),
@@ -136,6 +140,9 @@ function formatHotelResult(hotel, index) {
136
140
  if (hotel.distanceToCenter) {
137
141
  lines.push(` Location: ${hotel.distanceToCenter}`);
138
142
  }
143
+ if (hotel.availability) {
144
+ lines.push(` Availability: ${hotel.availability}`);
145
+ }
139
146
  if (hotel.amenities.length > 0) {
140
147
  lines.push(` Amenities: ${hotel.amenities.join(", ")}`);
141
148
  }
@@ -145,6 +152,9 @@ function formatHotelResult(hotel, index) {
145
152
  lines.push(` Why it matches: ${hotel.matchReasons.join(", ")}`);
146
153
  }
147
154
  }
155
+ if (hotel.thumbnailUrl) {
156
+ lines.push(` Image: ${hotel.thumbnailUrl}`);
157
+ }
148
158
  if (hotel.link) {
149
159
  // Shorten link for readability
150
160
  const shortLink = hotel.link.split("?")[0];
@@ -154,8 +164,8 @@ function formatHotelResult(hotel, index) {
154
164
  }
155
165
  // Create MCP server
156
166
  const server = new Server({
157
- name: "hotel-booking-mcp",
158
- version: "2.0.0",
167
+ name: "hotelzero",
168
+ version: "1.1.0",
159
169
  }, {
160
170
  capabilities: {
161
171
  tools: {},
@@ -173,7 +183,15 @@ const findHotelsInputSchema = {
173
183
  rooms: { type: "number", description: "Number of rooms", default: 1 },
174
184
  // Rating & Price
175
185
  minRating: { type: "number", description: "Minimum rating: 6=Pleasant, 7=Good, 8=Very Good, 9=Wonderful" },
176
- maxPrice: { type: "number", description: "Maximum price per night in USD" },
186
+ minPrice: { type: "number", description: "Minimum price per night" },
187
+ maxPrice: { type: "number", description: "Maximum price per night" },
188
+ // Currency & Sorting
189
+ currency: { type: "string", description: "Currency code (USD, EUR, GBP, JPY, etc.)", default: "USD" },
190
+ sortBy: {
191
+ type: "string",
192
+ description: "Sort results by",
193
+ enum: ["popularity", "price_lowest", "price_highest", "rating", "distance"]
194
+ },
177
195
  // Property Type
178
196
  propertyType: {
179
197
  type: "string",
@@ -333,11 +351,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
333
351
  checkOut: parsed.checkOut,
334
352
  guests: parsed.guests,
335
353
  rooms: parsed.rooms,
354
+ currency: parsed.currency,
355
+ sortBy: parsed.sortBy,
336
356
  };
337
357
  // Build filters object from all parsed parameters
338
358
  const filters = {};
339
359
  // Copy all filter properties
340
- const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms'].includes(k));
360
+ const filterKeys = Object.keys(parsed).filter(k => !['destination', 'checkIn', 'checkOut', 'guests', 'rooms', 'currency', 'sortBy'].includes(k));
341
361
  for (const key of filterKeys) {
342
362
  const value = parsed[key];
343
363
  if (value !== undefined && value !== null) {
@@ -389,6 +409,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
389
409
  activeFilters.push(parsed.hotelChain);
390
410
  if (parsed.minRating)
391
411
  activeFilters.push(`rating ≥${parsed.minRating}`);
412
+ if (parsed.minPrice)
413
+ activeFilters.push(`≥$${parsed.minPrice}/night`);
392
414
  if (parsed.maxPrice)
393
415
  activeFilters.push(`≤$${parsed.maxPrice}/night`);
394
416
  if (parsed.allInclusive)
@@ -399,6 +421,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
399
421
  activeFilters.push("diving");
400
422
  if (parsed.skiing)
401
423
  activeFilters.push("skiing");
424
+ if (parsed.currency && parsed.currency !== "USD")
425
+ activeFilters.push(`currency: ${parsed.currency}`);
426
+ if (parsed.sortBy)
427
+ activeFilters.push(`sorted by: ${parsed.sortBy.replace("_", " ")}`);
402
428
  const filtersLine = activeFilters.length > 0
403
429
  ? `Filters: ${activeFilters.join(", ")}\n\n`
404
430
  : "\n";
@@ -486,7 +512,7 @@ process.on("SIGTERM", async () => {
486
512
  async function main() {
487
513
  const transport = new StdioServerTransport();
488
514
  await server.connect(transport);
489
- console.error("HotelZero v1.0.0 running on stdio");
515
+ console.error("HotelZero v1.1.0 running on stdio");
490
516
  }
491
517
  main().catch((error) => {
492
518
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hotelzero",
3
- "version": "1.0.0",
3
+ "version": "1.1.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",
7
7
  "type": "module",
8
8
  "bin": {
9
- "hotelzero": "./dist/index.js"
9
+ "hotelzero": "dist/index.js"
10
10
  },
11
11
  "files": [
12
12
  "dist"