hotelzero 1.13.0 → 1.15.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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import { chromium } from "playwright";
2
+ async function debugCheckin() {
3
+ const browser = await chromium.launch({ headless: true });
4
+ const context = await browser.newContext({
5
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
6
+ });
7
+ const page = await context.newPage();
8
+ await page.goto("https://www.booking.com/hotel/fr/lejardindecluny.html", {
9
+ waitUntil: 'networkidle',
10
+ timeout: 60000
11
+ });
12
+ await page.waitForTimeout(2000);
13
+ const result = await page.evaluate(() => {
14
+ const w = window;
15
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
16
+ if (!cache)
17
+ return { error: 'No cache' };
18
+ const propertyKey = Object.keys(cache).find(k => k.startsWith('Property:{"id":'));
19
+ if (!propertyKey)
20
+ return { error: 'No property key' };
21
+ const property = cache[propertyKey];
22
+ return {
23
+ hasHouseRules: !!property?.houseRules,
24
+ houseRules: property?.houseRules,
25
+ propertyKeys: Object.keys(property).filter(k => k.includes('house') || k.includes('check'))
26
+ };
27
+ });
28
+ console.log(JSON.stringify(result, null, 2));
29
+ await browser.close();
30
+ }
31
+ debugCheckin().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ // Debug extraction on actual URL
2
+ import { chromium } from "playwright";
3
+ async function debugExtraction() {
4
+ const browser = await chromium.launch({ headless: true });
5
+ const context = await browser.newContext({
6
+ 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"
7
+ });
8
+ const page = await context.newPage();
9
+ // Use URL format WITH country code
10
+ const hotelUrl = "https://www.booking.com/hotel/fr/la-sanguine.html";
11
+ console.log("Navigating to:", hotelUrl);
12
+ await page.goto(hotelUrl, { waitUntil: 'networkidle', timeout: 60000 });
13
+ await page.waitForTimeout(2000);
14
+ console.log("Final URL:", page.url());
15
+ const result = await page.evaluate(() => {
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ const w = window;
18
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
19
+ if (!cache) {
20
+ return { error: 'No cache found', hasCapla: !!w.__caplaDataStore };
21
+ }
22
+ // Find Property key
23
+ const propertyKey = Object.keys(cache).find(k => k.startsWith('Property:{"id":'));
24
+ if (!propertyKey) {
25
+ return {
26
+ error: 'No Property key found',
27
+ allKeys: Object.keys(cache).slice(0, 30),
28
+ rootQueryKeys: cache['ROOT_QUERY'] ? Object.keys(cache['ROOT_QUERY']) : []
29
+ };
30
+ }
31
+ const property = cache[propertyKey];
32
+ const idMatch = propertyKey.match(/Property:\{"id":(\d+)\}/);
33
+ const hotelId = idMatch ? idMatch[1] : null;
34
+ const basicDataKey = hotelId ? `BasicPropertyData:${hotelId}` : null;
35
+ const basicData = basicDataKey ? cache[basicDataKey] : null;
36
+ return {
37
+ propertyKey,
38
+ propertyName: property?.name,
39
+ basicDataName: basicData?.name,
40
+ hasReviews: !!property?.reviews,
41
+ reviewsCount: property?.reviews?.reviewsCount,
42
+ propertyKeys: Object.keys(property || {}).slice(0, 20)
43
+ };
44
+ });
45
+ console.log("\n=== EXTRACTION RESULT ===");
46
+ console.log(JSON.stringify(result, null, 2));
47
+ await browser.close();
48
+ }
49
+ debugExtraction().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ // Debug search results cache structure
2
+ import { chromium } from "playwright";
3
+ async function debugSearch() {
4
+ const browser = await chromium.launch({ headless: true });
5
+ const context = await browser.newContext({
6
+ 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"
7
+ });
8
+ const page = await context.newPage();
9
+ const searchUrl = "https://www.booking.com/searchresults.html?ss=Paris&checkin=2026-06-01&checkout=2026-06-03&group_adults=2&no_rooms=1";
10
+ console.log("Navigating to:", searchUrl);
11
+ await page.goto(searchUrl, { waitUntil: 'networkidle', timeout: 60000 });
12
+ await page.waitForTimeout(3000);
13
+ const result = await page.evaluate(() => {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const w = window;
16
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
17
+ if (!cache) {
18
+ return { error: 'No cache found' };
19
+ }
20
+ const rootQuery = cache['ROOT_QUERY'];
21
+ if (!rootQuery)
22
+ return { error: 'No ROOT_QUERY' };
23
+ const searchQueries = rootQuery.searchQueries;
24
+ if (!searchQueries)
25
+ return { error: 'No searchQueries' };
26
+ const searchKey = Object.keys(searchQueries).find(k => k.startsWith('search('));
27
+ if (!searchKey)
28
+ return { error: 'No search key' };
29
+ const searchOutput = searchQueries[searchKey];
30
+ const results = searchOutput?.results;
31
+ if (!results || !Array.isArray(results) || results.length === 0) {
32
+ return { error: 'No results' };
33
+ }
34
+ // Get first hotel's structure
35
+ const firstHotel = results[0];
36
+ return {
37
+ basicPropertyDataKeys: Object.keys(firstHotel.basicPropertyData || {}),
38
+ pageName: firstHotel.basicPropertyData?.pageName,
39
+ location: firstHotel.basicPropertyData?.location,
40
+ fullBasicData: JSON.stringify(firstHotel.basicPropertyData).substring(0, 2000)
41
+ };
42
+ });
43
+ console.log("\n=== SEARCH RESULT STRUCTURE ===");
44
+ console.log(JSON.stringify(result, null, 2));
45
+ await browser.close();
46
+ }
47
+ debugSearch().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ // Explore Apollo cache on hotel detail page
2
+ import { chromium } from "playwright";
3
+ import * as fs from "fs";
4
+ async function exploreHotelDetailCache() {
5
+ const browser = await chromium.launch({ headless: true });
6
+ const context = await browser.newContext({
7
+ 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"
8
+ });
9
+ const page = await context.newPage();
10
+ // Use a known hotel URL
11
+ const hotelUrl = "https://www.booking.com/hotel/fr/la-sanguine.html?checkin=2026-06-01&checkout=2026-06-03&group_adults=2&no_rooms=1";
12
+ console.log("Navigating to:", hotelUrl);
13
+ await page.goto(hotelUrl, { waitUntil: 'networkidle', timeout: 60000 });
14
+ await page.waitForTimeout(3000);
15
+ console.log('\nExploring Apollo cache on hotel detail page...\n');
16
+ // Extract cache structure
17
+ const cacheAnalysis = await page.evaluate(() => {
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ const w = window;
20
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
21
+ if (!cache)
22
+ return { error: 'No cache found' };
23
+ const rootQuery = cache['ROOT_QUERY'];
24
+ if (!rootQuery)
25
+ return { error: 'No ROOT_QUERY', cacheKeys: Object.keys(cache).slice(0, 50) };
26
+ return {
27
+ rootQueryKeys: Object.keys(rootQuery),
28
+ cacheKeys: Object.keys(cache).slice(0, 100),
29
+ // Look for property-related keys
30
+ propertyKeys: Object.keys(cache).filter(k => k.toLowerCase().includes('property') ||
31
+ k.toLowerCase().includes('hotel') ||
32
+ k.toLowerCase().includes('accommodation')),
33
+ };
34
+ });
35
+ console.log('=== CACHE ANALYSIS ===');
36
+ console.log('ROOT_QUERY keys:', cacheAnalysis.rootQueryKeys);
37
+ console.log('\nProperty-related keys:', cacheAnalysis.propertyKeys);
38
+ console.log('\nAll cache keys (first 100):', cacheAnalysis.cacheKeys);
39
+ // Look for specific hotel data
40
+ const hotelData = await page.evaluate(() => {
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ const w = window;
43
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
44
+ if (!cache)
45
+ return null;
46
+ const rootQuery = cache['ROOT_QUERY'];
47
+ if (!rootQuery)
48
+ return null;
49
+ // Find keys that might contain hotel details
50
+ const detailKeys = Object.keys(rootQuery).filter(k => k.includes('propertyPage') ||
51
+ k.includes('hotelPage') ||
52
+ k.includes('accommodation') ||
53
+ k.includes('basicPropertyData'));
54
+ const results = {};
55
+ for (const key of detailKeys) {
56
+ results[key] = rootQuery[key];
57
+ }
58
+ return {
59
+ detailKeys,
60
+ sampleData: JSON.stringify(results).substring(0, 5000)
61
+ };
62
+ });
63
+ console.log('\n=== HOTEL DETAIL DATA ===');
64
+ console.log('Detail keys:', hotelData?.detailKeys);
65
+ console.log('\nSample data:', hotelData?.sampleData);
66
+ // Save full cache for analysis
67
+ const fullCache = await page.evaluate(() => {
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ const w = window;
70
+ return w.__caplaDataStore?.apollo?.cache?.data?.data;
71
+ });
72
+ if (fullCache) {
73
+ fs.writeFileSync('/Users/matt/Developer/hotel-booking-mcp/hotel-detail-cache.json', JSON.stringify(fullCache, null, 2));
74
+ console.log('\nFull cache saved to: hotel-detail-cache.json');
75
+ }
76
+ await browser.close();
77
+ }
78
+ exploreHotelDetailCache().catch(console.error);
package/dist/index.js CHANGED
@@ -550,7 +550,7 @@ function formatPriceCalendarResult(result) {
550
550
  // Create MCP server
551
551
  const server = new Server({
552
552
  name: "hotelzero",
553
- version: "1.13.0",
553
+ version: "1.14.0",
554
554
  }, {
555
555
  capabilities: {
556
556
  tools: {},
@@ -1051,7 +1051,7 @@ process.on("SIGTERM", async () => {
1051
1051
  async function main() {
1052
1052
  const transport = new StdioServerTransport();
1053
1053
  await server.connect(transport);
1054
- logger.info({ version: "1.11.0", transport: "stdio" }, "HotelZero server started");
1054
+ logger.info({ version: "1.14.0", transport: "stdio" }, "HotelZero server started");
1055
1055
  }
1056
1056
  main().catch((error) => {
1057
1057
  logger.fatal({ err: error }, "Fatal error during server startup");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,161 @@
1
+ // Extract hotel search results from Booking.com Apollo cache
2
+ import { chromium } from "playwright";
3
+ import * as fs from "fs";
4
+ async function extractSearchResults() {
5
+ const browser = await chromium.launch({ headless: true });
6
+ const context = await browser.newContext({
7
+ 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"
8
+ });
9
+ const page = await context.newPage();
10
+ const searchUrl = "https://www.booking.com/searchresults.html?ss=Paris&checkin=2026-06-01&checkout=2026-06-03&group_adults=2&no_rooms=1";
11
+ console.log("Navigating to:", searchUrl);
12
+ await page.goto(searchUrl, { waitUntil: 'networkidle', timeout: 60000 });
13
+ await page.waitForTimeout(3000);
14
+ console.log('\nExtracting search results from Apollo cache...\n');
15
+ // Extract search results from Apollo cache
16
+ const searchResults = await page.evaluate(() => {
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const w = window;
19
+ const cache = w.__caplaDataStore?.apollo?.cache?.data?.data;
20
+ if (!cache)
21
+ return null;
22
+ const rootQuery = cache['ROOT_QUERY'];
23
+ if (!rootQuery)
24
+ return null;
25
+ // searchQueries is at ROOT_QUERY.searchQueries
26
+ const searchQueries = rootQuery.searchQueries;
27
+ if (!searchQueries)
28
+ return null;
29
+ // Find the search key (it's a complex key with the query parameters)
30
+ const searchKey = Object.keys(searchQueries).find(k => k.startsWith('search('));
31
+ if (!searchKey)
32
+ return { error: 'No search key found', keys: Object.keys(searchQueries) };
33
+ const searchOutput = searchQueries[searchKey];
34
+ if (!searchOutput)
35
+ return { error: 'No search output', searchKey };
36
+ // Get the results array
37
+ const results = searchOutput.results;
38
+ if (!results || !Array.isArray(results)) {
39
+ return { error: 'No results array', searchOutputKeys: Object.keys(searchOutput) };
40
+ }
41
+ return {
42
+ success: true,
43
+ totalResults: searchOutput.pagination?.nbResultsTotal || results.length,
44
+ resultsInPage: results.length,
45
+ results
46
+ };
47
+ });
48
+ if (!searchResults) {
49
+ console.log('No cache found');
50
+ await browser.close();
51
+ return;
52
+ }
53
+ if ('error' in searchResults) {
54
+ console.log('Error:', searchResults.error);
55
+ console.log('Details:', searchResults);
56
+ await browser.close();
57
+ return;
58
+ }
59
+ console.log(`Total hotels available: ${searchResults.totalResults}`);
60
+ console.log(`Hotels in this page: ${searchResults.resultsInPage}`);
61
+ // Save first result for analysis
62
+ const firstResult = searchResults.results[0];
63
+ if (firstResult) {
64
+ fs.writeFileSync('/Users/matt/Developer/hotel-booking-mcp/first-hotel-result.json', JSON.stringify(firstResult, null, 2));
65
+ console.log('\nFirst result saved to: first-hotel-result.json');
66
+ console.log('\nFirst result top-level keys:', Object.keys(firstResult));
67
+ }
68
+ // Map results to our format
69
+ console.log('\n=== SAMPLE MAPPED RESULTS ===');
70
+ for (let i = 0; i < Math.min(5, searchResults.results.length); i++) {
71
+ const hotel = searchResults.results[i];
72
+ if (!hotel)
73
+ continue;
74
+ const mapped = mapGraphQLToHotelResult(hotel);
75
+ console.log(`\n${i + 1}. ${mapped.name}`);
76
+ console.log(` Price: ${mapped.priceDisplay} (${mapped.price})`);
77
+ console.log(` Rating: ${mapped.rating} "${mapped.ratingText}" (${mapped.reviewCount} reviews)`);
78
+ console.log(` Location: ${mapped.location}`);
79
+ console.log(` Distance: ${mapped.distanceToCenter}`);
80
+ console.log(` Link: ${mapped.link}`);
81
+ console.log(` Image: ${mapped.thumbnailUrl?.substring(0, 80)}...`);
82
+ }
83
+ await browser.close();
84
+ }
85
+ function mapGraphQLToHotelResult(hotel) {
86
+ // Extract nested objects
87
+ const displayName = hotel.displayName;
88
+ const basicPropertyData = hotel.basicPropertyData;
89
+ const location = hotel.location;
90
+ const priceDisplayInfoIrene = hotel.priceDisplayInfoIrene;
91
+ const reviews = hotel.reviews;
92
+ // Name
93
+ const name = displayName?.text || basicPropertyData?.pageName || 'Unknown';
94
+ // Price - navigate the nested structure
95
+ let price = null;
96
+ let priceDisplay = 'Price not shown';
97
+ if (priceDisplayInfoIrene?.displayPrice) {
98
+ const displayPrice = priceDisplayInfoIrene.displayPrice;
99
+ // Try different price fields
100
+ const amountPerStay = displayPrice.amountPerStay;
101
+ if (amountPerStay) {
102
+ priceDisplay = amountPerStay.amountRounded || amountPerStay.amount || priceDisplay;
103
+ price = amountPerStay.amountUnformatted ?? null;
104
+ }
105
+ }
106
+ // Rating
107
+ let rating = null;
108
+ let ratingText = '';
109
+ let reviewCount = null;
110
+ if (reviews) {
111
+ rating = reviews.totalScore ?? null;
112
+ ratingText = reviews.totalScoreTextTag?.translation || '';
113
+ reviewCount = reviews.reviewsCount ?? null;
114
+ }
115
+ // Location
116
+ const displayLocation = location?.displayLocation || '';
117
+ const mainDistance = location?.mainDistance || '';
118
+ // Thumbnail URL - construct full URL
119
+ let thumbnailUrl = null;
120
+ if (basicPropertyData?.photos?.main) {
121
+ const mainPhoto = basicPropertyData.photos.main;
122
+ const relativeUrl = mainPhoto.highResJpegUrl?.relativeUrl ||
123
+ mainPhoto.highResUrl?.relativeUrl ||
124
+ mainPhoto.lowResJpegUrl?.relativeUrl;
125
+ if (relativeUrl) {
126
+ thumbnailUrl = `https://cf.bstatic.com${relativeUrl}`;
127
+ }
128
+ }
129
+ // Link
130
+ let link = '';
131
+ if (basicPropertyData?.pageName) {
132
+ link = `https://www.booking.com/hotel/${basicPropertyData.pageName}.html`;
133
+ }
134
+ // Extract amenities from various places
135
+ const amenities = [];
136
+ // propertySustainability
137
+ if (hotel.propertySustainability?.isSustainable) {
138
+ amenities.push('Sustainable');
139
+ }
140
+ // Check policies for free cancellation
141
+ const highlights = [];
142
+ if (hotel.policies?.enableJap498702498702FreeCancellation498702498702) {
143
+ highlights.push('Free Cancellation');
144
+ }
145
+ return {
146
+ name,
147
+ price,
148
+ priceDisplay,
149
+ rating,
150
+ ratingText,
151
+ reviewCount,
152
+ location: displayLocation,
153
+ distanceToCenter: mainDistance,
154
+ amenities,
155
+ highlights,
156
+ link,
157
+ thumbnailUrl,
158
+ availability: null,
159
+ };
160
+ }
161
+ extractSearchResults().catch(console.error);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Verify that API extraction is actually working for all MCP features
3
+ */
4
+ import { HotelBrowser } from './browser.js';
5
+ async function verifyAPIExtraction() {
6
+ const browser = new HotelBrowser();
7
+ console.log('\n========================================');
8
+ console.log('VERIFYING API EXTRACTION FOR MCP FEATURES');
9
+ console.log('========================================\n');
10
+ try {
11
+ await browser.init(true);
12
+ // Test 1: searchHotels - should use API extraction
13
+ console.log('TEST 1: searchHotels');
14
+ console.log('-------------------');
15
+ const searchResults = await browser.searchHotels({
16
+ destination: 'Paris',
17
+ checkIn: '2026-06-01',
18
+ checkOut: '2026-06-03',
19
+ guests: 2,
20
+ rooms: 1,
21
+ limit: 3,
22
+ });
23
+ if (searchResults.length === 0) {
24
+ console.log('❌ FAIL: No search results returned');
25
+ }
26
+ else {
27
+ const firstResult = searchResults[0];
28
+ console.log(` Results: ${searchResults.length}`);
29
+ console.log(` First hotel: ${firstResult.name}`);
30
+ console.log(` Has rating: ${firstResult.rating !== null}`);
31
+ console.log(` Has price: ${firstResult.price !== null}`);
32
+ console.log(` Link format: ${firstResult.link}`);
33
+ // Check if link has country code (required for API extraction on detail page)
34
+ const hasCountryCode = /\/hotel\/[a-z]{2}\//.test(firstResult.link);
35
+ console.log(` Link has country code: ${hasCountryCode ? '✅ YES' : '❌ NO'}`);
36
+ if (!hasCountryCode) {
37
+ console.log(' ⚠️ WARNING: Links without country code will break API extraction on detail pages!');
38
+ }
39
+ }
40
+ // Test 2: getHotelDetailsForComparison - should use API extraction
41
+ console.log('\nTEST 2: getHotelDetailsForComparison');
42
+ console.log('------------------------------------');
43
+ if (searchResults.length > 0 && searchResults[0].link) {
44
+ const details = await browser.getHotelDetailsForComparison(searchResults[0].link);
45
+ console.log(` Name: ${details.name}`);
46
+ console.log(` Rating: ${details.rating}`);
47
+ console.log(` Reviews: ${details.reviewCount}`);
48
+ console.log(` Address: ${details.address || '(empty)'}`);
49
+ console.log(` Check-in: ${details.checkInTime || '(empty)'}`);
50
+ console.log(` Facilities: ${details.popularFacilities.length}`);
51
+ console.log(` Photos: ${details.photos.length}`);
52
+ // Verify this looks like real API data, not DOM scraping artifacts
53
+ const isLikelyAPIData = details.name &&
54
+ details.name.length > 3 &&
55
+ !details.name.includes('Verified reviews') &&
56
+ details.address &&
57
+ details.address.includes(',');
58
+ console.log(` Looks like API data: ${isLikelyAPIData ? '✅ YES' : '❌ NO (might be DOM fallback)'}`);
59
+ }
60
+ // Test 3: checkAvailability - currently uses DOM scraping
61
+ console.log('\nTEST 3: checkAvailability');
62
+ console.log('-------------------------');
63
+ if (searchResults.length > 0 && searchResults[0].link) {
64
+ const availability = await browser.checkAvailability({
65
+ hotelUrl: searchResults[0].link,
66
+ checkIn: '2026-06-01',
67
+ checkOut: '2026-06-03',
68
+ guests: 2,
69
+ rooms: 1,
70
+ });
71
+ console.log(` Hotel: ${availability.hotelName}`);
72
+ console.log(` Available: ${availability.isAvailable}`);
73
+ console.log(` Room options: ${availability.rooms.length}`);
74
+ console.log(` Message: ${availability.message || '(none)'}`);
75
+ // Check if hotel name looks like API data
76
+ const nameIsValid = availability.hotelName &&
77
+ availability.hotelName.length > 3 &&
78
+ !availability.hotelName.includes('Verified');
79
+ console.log(` Name looks valid: ${nameIsValid ? '✅ YES' : '❌ NO'}`);
80
+ console.log(` ⚠️ Note: checkAvailability does NOT use API extraction yet`);
81
+ }
82
+ // Test 4: getReviews - currently uses DOM scraping
83
+ console.log('\nTEST 4: getReviews');
84
+ console.log('------------------');
85
+ if (searchResults.length > 0 && searchResults[0].link) {
86
+ try {
87
+ const reviews = await browser.getReviews(searchResults[0].link);
88
+ console.log(` Hotel: ${reviews.hotelName}`);
89
+ console.log(` Overall rating: ${reviews.overallRating}`);
90
+ console.log(` Total reviews: ${reviews.totalReviews}`);
91
+ console.log(` Categories: ${Object.keys(reviews.ratingBreakdown || {}).length}`);
92
+ console.log(` Sample reviews: ${reviews.reviews.length}`);
93
+ console.log(` ⚠️ Note: getReviews does NOT use API extraction yet`);
94
+ }
95
+ catch (e) {
96
+ console.log(` ⚠️ getReviews failed: ${e.message}`);
97
+ }
98
+ }
99
+ console.log('\n========================================');
100
+ console.log('SUMMARY');
101
+ console.log('========================================');
102
+ console.log('✅ searchHotels: Uses API extraction');
103
+ console.log('✅ getHotelDetailsForComparison: Uses API extraction');
104
+ console.log('⚠️ checkAvailability: DOM scraping only');
105
+ console.log('⚠️ getReviews: DOM scraping only');
106
+ console.log('⚠️ getPriceCalendar: DOM scraping only');
107
+ console.log('\n');
108
+ }
109
+ catch (error) {
110
+ console.error('Error:', error);
111
+ }
112
+ finally {
113
+ await browser.close();
114
+ }
115
+ }
116
+ verifyAPIExtraction().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hotelzero",
3
- "version": "1.13.0",
3
+ "version": "1.15.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",