@striderlabs/mcp-booking 0.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/src/index.ts ADDED
@@ -0,0 +1,865 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Strider Labs Booking.com MCP Server
5
+ *
6
+ * MCP server that gives AI agents the ability to search hotels, check availability,
7
+ * manage reservations, and more on Booking.com via browser automation.
8
+ * https://striderlabs.ai
9
+ */
10
+
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import {
14
+ CallToolRequestSchema,
15
+ ListToolsRequestSchema,
16
+ } from "@modelcontextprotocol/sdk/types.js";
17
+
18
+ import {
19
+ checkLoginStatus,
20
+ initiateLogin,
21
+ searchProperties,
22
+ getPropertyDetails,
23
+ checkAvailability,
24
+ getPrices,
25
+ filterResults,
26
+ sortResults,
27
+ saveProperty,
28
+ bookRoom,
29
+ getReservations,
30
+ cancelReservation,
31
+ getPropertyReviews,
32
+ closeBrowser,
33
+ } from "./browser.js";
34
+ import { loadSessionInfo, clearAuthData, getConfigDir } from "./auth.js";
35
+
36
+ // Initialize server
37
+ const server = new Server(
38
+ {
39
+ name: "strider-booking",
40
+ version: "0.1.0",
41
+ },
42
+ {
43
+ capabilities: {
44
+ tools: {},
45
+ },
46
+ }
47
+ );
48
+
49
+ // Tool definitions
50
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
51
+ return {
52
+ tools: [
53
+ {
54
+ name: "booking_status",
55
+ description:
56
+ "Check Booking.com login status and session info. Use this to verify authentication before performing other actions.",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {},
60
+ },
61
+ },
62
+ {
63
+ name: "booking_login",
64
+ description:
65
+ "Initiate Booking.com login flow. Returns a URL and instructions for the user to complete login manually. After logging in, use booking_status to verify.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {},
69
+ },
70
+ },
71
+ {
72
+ name: "booking_logout",
73
+ description:
74
+ "Clear saved Booking.com session and cookies. Use this to log out or reset authentication state.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {},
78
+ },
79
+ },
80
+ {
81
+ name: "booking_search",
82
+ description:
83
+ "Search for hotels and properties on Booking.com. Returns property names, ratings, prices, and availability indicators.",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ destination: {
88
+ type: "string",
89
+ description:
90
+ "Destination to search (e.g. 'Paris', 'New York', 'Rome, Italy')",
91
+ },
92
+ checkIn: {
93
+ type: "string",
94
+ description: "Check-in date in YYYY-MM-DD format",
95
+ },
96
+ checkOut: {
97
+ type: "string",
98
+ description: "Check-out date in YYYY-MM-DD format",
99
+ },
100
+ adults: {
101
+ type: "number",
102
+ description: "Number of adults (default: 2)",
103
+ },
104
+ rooms: {
105
+ type: "number",
106
+ description: "Number of rooms (default: 1)",
107
+ },
108
+ children: {
109
+ type: "number",
110
+ description: "Number of children (default: 0)",
111
+ },
112
+ maxResults: {
113
+ type: "number",
114
+ description: "Maximum results to return (default: 10, max: 50)",
115
+ },
116
+ },
117
+ required: ["destination", "checkIn", "checkOut"],
118
+ },
119
+ },
120
+ {
121
+ name: "booking_get_property",
122
+ description:
123
+ "Get detailed information about a specific property, including amenities, description, policies, and photos.",
124
+ inputSchema: {
125
+ type: "object",
126
+ properties: {
127
+ propertyUrl: {
128
+ type: "string",
129
+ description:
130
+ "Full URL of the property page, or a propertyId from booking_search results",
131
+ },
132
+ },
133
+ required: ["propertyUrl"],
134
+ },
135
+ },
136
+ {
137
+ name: "booking_check_availability",
138
+ description:
139
+ "Check room availability for a specific property and date range. Returns available room types and options.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {
143
+ propertyUrl: {
144
+ type: "string",
145
+ description: "Full property URL or propertyId from search results",
146
+ },
147
+ checkIn: {
148
+ type: "string",
149
+ description: "Check-in date in YYYY-MM-DD format",
150
+ },
151
+ checkOut: {
152
+ type: "string",
153
+ description: "Check-out date in YYYY-MM-DD format",
154
+ },
155
+ adults: {
156
+ type: "number",
157
+ description: "Number of adults (default: 2)",
158
+ },
159
+ rooms: {
160
+ type: "number",
161
+ description: "Number of rooms (default: 1)",
162
+ },
163
+ },
164
+ required: ["propertyUrl", "checkIn", "checkOut"],
165
+ },
166
+ },
167
+ {
168
+ name: "booking_get_prices",
169
+ description:
170
+ "Get current prices for a property for specific dates. Returns room options with pricing details and the lowest available price.",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {
174
+ propertyUrl: {
175
+ type: "string",
176
+ description: "Full property URL or propertyId from search results",
177
+ },
178
+ checkIn: {
179
+ type: "string",
180
+ description: "Check-in date in YYYY-MM-DD format",
181
+ },
182
+ checkOut: {
183
+ type: "string",
184
+ description: "Check-out date in YYYY-MM-DD format",
185
+ },
186
+ adults: {
187
+ type: "number",
188
+ description: "Number of adults (default: 2)",
189
+ },
190
+ rooms: {
191
+ type: "number",
192
+ description: "Number of rooms (default: 1)",
193
+ },
194
+ },
195
+ required: ["propertyUrl", "checkIn", "checkOut"],
196
+ },
197
+ },
198
+ {
199
+ name: "booking_filter_results",
200
+ description:
201
+ "Filter the most recent booking_search results by price, rating, amenities, or keywords. Returns a filtered subset.",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ minPrice: {
206
+ type: "number",
207
+ description: "Minimum price per night",
208
+ },
209
+ maxPrice: {
210
+ type: "number",
211
+ description: "Maximum price per night",
212
+ },
213
+ minRating: {
214
+ type: "number",
215
+ description: "Minimum review score (0-10)",
216
+ },
217
+ freeCancellation: {
218
+ type: "boolean",
219
+ description: "Only show properties with free cancellation",
220
+ },
221
+ breakfastIncluded: {
222
+ type: "boolean",
223
+ description: "Only show properties with breakfast included",
224
+ },
225
+ stars: {
226
+ type: "number",
227
+ description: "Filter by star rating (1-5)",
228
+ },
229
+ keyword: {
230
+ type: "string",
231
+ description: "Filter by keyword in property name or location",
232
+ },
233
+ },
234
+ },
235
+ },
236
+ {
237
+ name: "booking_sort_results",
238
+ description:
239
+ "Sort the most recent booking_search results. Options: price_asc, price_desc, rating, distance, reviews.",
240
+ inputSchema: {
241
+ type: "object",
242
+ properties: {
243
+ sortBy: {
244
+ type: "string",
245
+ enum: ["price_asc", "price_desc", "rating", "distance", "reviews"],
246
+ description:
247
+ "Sort criteria: price_asc (cheapest first), price_desc (most expensive first), rating (highest rated), distance (closest to center), reviews (most reviewed)",
248
+ },
249
+ },
250
+ required: ["sortBy"],
251
+ },
252
+ },
253
+ {
254
+ name: "booking_save_property",
255
+ description:
256
+ "Save a property to your Booking.com wishlist/favorites. Requires being logged in.",
257
+ inputSchema: {
258
+ type: "object",
259
+ properties: {
260
+ propertyUrl: {
261
+ type: "string",
262
+ description: "Full property URL or propertyId from search results",
263
+ },
264
+ },
265
+ required: ["propertyUrl"],
266
+ },
267
+ },
268
+ {
269
+ name: "booking_book",
270
+ description:
271
+ "Book a room at a property. IMPORTANT: Set confirm=true only when you have explicit user confirmation. Without confirm=true, returns a preview instead of placing the booking.",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ propertyUrl: {
276
+ type: "string",
277
+ description: "Full property URL or propertyId from search results",
278
+ },
279
+ checkIn: {
280
+ type: "string",
281
+ description: "Check-in date in YYYY-MM-DD format",
282
+ },
283
+ checkOut: {
284
+ type: "string",
285
+ description: "Check-out date in YYYY-MM-DD format",
286
+ },
287
+ adults: {
288
+ type: "number",
289
+ description: "Number of adults (default: 2)",
290
+ },
291
+ rooms: {
292
+ type: "number",
293
+ description: "Number of rooms (default: 1)",
294
+ },
295
+ confirm: {
296
+ type: "boolean",
297
+ description:
298
+ "Set to true to actually place the booking. If false or omitted, returns a preview. NEVER set to true without explicit user confirmation.",
299
+ },
300
+ },
301
+ required: ["propertyUrl", "checkIn", "checkOut"],
302
+ },
303
+ },
304
+ {
305
+ name: "booking_get_reservations",
306
+ description:
307
+ "Get all current and upcoming reservations from your Booking.com account. Requires being logged in.",
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {},
311
+ },
312
+ },
313
+ {
314
+ name: "booking_cancel_reservation",
315
+ description:
316
+ "Cancel an existing reservation. IMPORTANT: Set confirm=true only when you have explicit user confirmation. This action cannot be undone.",
317
+ inputSchema: {
318
+ type: "object",
319
+ properties: {
320
+ reservationId: {
321
+ type: "string",
322
+ description:
323
+ "The reservation/booking ID from booking_get_reservations",
324
+ },
325
+ confirm: {
326
+ type: "boolean",
327
+ description:
328
+ "Set to true to actually cancel. If false or omitted, returns a warning instead. NEVER set to true without explicit user confirmation.",
329
+ },
330
+ },
331
+ required: ["reservationId"],
332
+ },
333
+ },
334
+ {
335
+ name: "booking_get_reviews",
336
+ description:
337
+ "Get guest reviews for a property, including scores, comments, and reviewer details.",
338
+ inputSchema: {
339
+ type: "object",
340
+ properties: {
341
+ propertyUrl: {
342
+ type: "string",
343
+ description: "Full property URL or propertyId from search results",
344
+ },
345
+ maxReviews: {
346
+ type: "number",
347
+ description:
348
+ "Maximum number of reviews to return (default: 10, max: 50)",
349
+ },
350
+ },
351
+ required: ["propertyUrl"],
352
+ },
353
+ },
354
+ ],
355
+ };
356
+ });
357
+
358
+ // Tool execution
359
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
360
+ const { name, arguments: args } = request.params;
361
+
362
+ try {
363
+ switch (name) {
364
+ case "booking_status": {
365
+ const sessionInfo = loadSessionInfo();
366
+ const liveStatus = await checkLoginStatus();
367
+
368
+ return {
369
+ content: [
370
+ {
371
+ type: "text",
372
+ text: JSON.stringify(
373
+ {
374
+ success: true,
375
+ session: liveStatus,
376
+ configDir: getConfigDir(),
377
+ cached: sessionInfo,
378
+ message: liveStatus.isLoggedIn
379
+ ? `Logged in${liveStatus.userEmail ? ` as ${liveStatus.userEmail}` : liveStatus.userName ? ` as ${liveStatus.userName}` : ""}`
380
+ : "Not logged in. Use booking_login to authenticate.",
381
+ },
382
+ null,
383
+ 2
384
+ ),
385
+ },
386
+ ],
387
+ };
388
+ }
389
+
390
+ case "booking_login": {
391
+ const result = await initiateLogin();
392
+
393
+ return {
394
+ content: [
395
+ {
396
+ type: "text",
397
+ text: JSON.stringify(
398
+ {
399
+ success: true,
400
+ ...result,
401
+ },
402
+ null,
403
+ 2
404
+ ),
405
+ },
406
+ ],
407
+ };
408
+ }
409
+
410
+ case "booking_logout": {
411
+ clearAuthData();
412
+ await closeBrowser();
413
+
414
+ return {
415
+ content: [
416
+ {
417
+ type: "text",
418
+ text: JSON.stringify({
419
+ success: true,
420
+ message: "Logged out. Session and cookies cleared.",
421
+ }),
422
+ },
423
+ ],
424
+ };
425
+ }
426
+
427
+ case "booking_search": {
428
+ const {
429
+ destination,
430
+ checkIn,
431
+ checkOut,
432
+ adults = 2,
433
+ rooms = 1,
434
+ children = 0,
435
+ maxResults = 10,
436
+ } = args as {
437
+ destination: string;
438
+ checkIn: string;
439
+ checkOut: string;
440
+ adults?: number;
441
+ rooms?: number;
442
+ children?: number;
443
+ maxResults?: number;
444
+ };
445
+
446
+ const properties = await searchProperties(
447
+ destination,
448
+ checkIn,
449
+ checkOut,
450
+ adults,
451
+ rooms,
452
+ children,
453
+ Math.min(maxResults, 50)
454
+ );
455
+
456
+ return {
457
+ content: [
458
+ {
459
+ type: "text",
460
+ text: JSON.stringify(
461
+ {
462
+ success: true,
463
+ destination,
464
+ checkIn,
465
+ checkOut,
466
+ adults,
467
+ rooms,
468
+ count: properties.length,
469
+ properties,
470
+ },
471
+ null,
472
+ 2
473
+ ),
474
+ },
475
+ ],
476
+ };
477
+ }
478
+
479
+ case "booking_get_property": {
480
+ const { propertyUrl } = args as { propertyUrl: string };
481
+ const details = await getPropertyDetails(propertyUrl);
482
+
483
+ return {
484
+ content: [
485
+ {
486
+ type: "text",
487
+ text: JSON.stringify(
488
+ {
489
+ success: true,
490
+ property: details,
491
+ },
492
+ null,
493
+ 2
494
+ ),
495
+ },
496
+ ],
497
+ };
498
+ }
499
+
500
+ case "booking_check_availability": {
501
+ const {
502
+ propertyUrl,
503
+ checkIn,
504
+ checkOut,
505
+ adults = 2,
506
+ rooms = 1,
507
+ } = args as {
508
+ propertyUrl: string;
509
+ checkIn: string;
510
+ checkOut: string;
511
+ adults?: number;
512
+ rooms?: number;
513
+ };
514
+
515
+ const result = await checkAvailability(
516
+ propertyUrl,
517
+ checkIn,
518
+ checkOut,
519
+ adults,
520
+ rooms
521
+ );
522
+
523
+ return {
524
+ content: [
525
+ {
526
+ type: "text",
527
+ text: JSON.stringify(
528
+ {
529
+ success: true,
530
+ ...result,
531
+ },
532
+ null,
533
+ 2
534
+ ),
535
+ },
536
+ ],
537
+ };
538
+ }
539
+
540
+ case "booking_get_prices": {
541
+ const {
542
+ propertyUrl,
543
+ checkIn,
544
+ checkOut,
545
+ adults = 2,
546
+ rooms = 1,
547
+ } = args as {
548
+ propertyUrl: string;
549
+ checkIn: string;
550
+ checkOut: string;
551
+ adults?: number;
552
+ rooms?: number;
553
+ };
554
+
555
+ const result = await getPrices(
556
+ propertyUrl,
557
+ checkIn,
558
+ checkOut,
559
+ adults,
560
+ rooms
561
+ );
562
+
563
+ return {
564
+ content: [
565
+ {
566
+ type: "text",
567
+ text: JSON.stringify(
568
+ {
569
+ success: true,
570
+ ...result,
571
+ },
572
+ null,
573
+ 2
574
+ ),
575
+ },
576
+ ],
577
+ };
578
+ }
579
+
580
+ case "booking_filter_results": {
581
+ const filters = args as {
582
+ minPrice?: number;
583
+ maxPrice?: number;
584
+ minRating?: number;
585
+ freeCancellation?: boolean;
586
+ breakfastIncluded?: boolean;
587
+ stars?: number;
588
+ keyword?: string;
589
+ };
590
+
591
+ const filtered = filterResults(filters);
592
+
593
+ return {
594
+ content: [
595
+ {
596
+ type: "text",
597
+ text: JSON.stringify(
598
+ {
599
+ success: true,
600
+ filters,
601
+ count: filtered.length,
602
+ properties: filtered,
603
+ },
604
+ null,
605
+ 2
606
+ ),
607
+ },
608
+ ],
609
+ };
610
+ }
611
+
612
+ case "booking_sort_results": {
613
+ const { sortBy } = args as {
614
+ sortBy:
615
+ | "price_asc"
616
+ | "price_desc"
617
+ | "rating"
618
+ | "distance"
619
+ | "reviews";
620
+ };
621
+
622
+ const sorted = sortResults(sortBy);
623
+
624
+ return {
625
+ content: [
626
+ {
627
+ type: "text",
628
+ text: JSON.stringify(
629
+ {
630
+ success: true,
631
+ sortBy,
632
+ count: sorted.length,
633
+ properties: sorted,
634
+ },
635
+ null,
636
+ 2
637
+ ),
638
+ },
639
+ ],
640
+ };
641
+ }
642
+
643
+ case "booking_save_property": {
644
+ const { propertyUrl } = args as { propertyUrl: string };
645
+ const result = await saveProperty(propertyUrl);
646
+
647
+ return {
648
+ content: [
649
+ {
650
+ type: "text",
651
+ text: JSON.stringify(
652
+ {
653
+ success: result.success,
654
+ message: result.message,
655
+ },
656
+ null,
657
+ 2
658
+ ),
659
+ },
660
+ ],
661
+ };
662
+ }
663
+
664
+ case "booking_book": {
665
+ const {
666
+ propertyUrl,
667
+ checkIn,
668
+ checkOut,
669
+ adults = 2,
670
+ rooms = 1,
671
+ confirm = false,
672
+ } = args as {
673
+ propertyUrl: string;
674
+ checkIn: string;
675
+ checkOut: string;
676
+ adults?: number;
677
+ rooms?: number;
678
+ confirm?: boolean;
679
+ };
680
+
681
+ if (!confirm) {
682
+ return {
683
+ content: [
684
+ {
685
+ type: "text",
686
+ text: JSON.stringify(
687
+ {
688
+ success: true,
689
+ requiresConfirmation: true,
690
+ preview: {
691
+ propertyUrl,
692
+ checkIn,
693
+ checkOut,
694
+ adults,
695
+ rooms,
696
+ },
697
+ message:
698
+ "Booking not placed. To proceed, call booking_book with confirm=true. " +
699
+ "IMPORTANT: Only do this after getting explicit user confirmation.",
700
+ },
701
+ null,
702
+ 2
703
+ ),
704
+ },
705
+ ],
706
+ };
707
+ }
708
+
709
+ const result = await bookRoom(
710
+ propertyUrl,
711
+ checkIn,
712
+ checkOut,
713
+ adults,
714
+ rooms,
715
+ true
716
+ );
717
+
718
+ return {
719
+ content: [
720
+ {
721
+ type: "text",
722
+ text: JSON.stringify({ success: true, ...result }, null, 2),
723
+ },
724
+ ],
725
+ };
726
+ }
727
+
728
+ case "booking_get_reservations": {
729
+ const reservations = await getReservations();
730
+
731
+ return {
732
+ content: [
733
+ {
734
+ type: "text",
735
+ text: JSON.stringify(
736
+ {
737
+ success: true,
738
+ count: reservations.length,
739
+ reservations,
740
+ },
741
+ null,
742
+ 2
743
+ ),
744
+ },
745
+ ],
746
+ };
747
+ }
748
+
749
+ case "booking_cancel_reservation": {
750
+ const { reservationId, confirm = false } = args as {
751
+ reservationId: string;
752
+ confirm?: boolean;
753
+ };
754
+
755
+ const result = await cancelReservation(reservationId, confirm);
756
+
757
+ return {
758
+ content: [
759
+ {
760
+ type: "text",
761
+ text: JSON.stringify(
762
+ {
763
+ success: result.success,
764
+ message: result.message,
765
+ },
766
+ null,
767
+ 2
768
+ ),
769
+ },
770
+ ],
771
+ };
772
+ }
773
+
774
+ case "booking_get_reviews": {
775
+ const { propertyUrl, maxReviews = 10 } = args as {
776
+ propertyUrl: string;
777
+ maxReviews?: number;
778
+ };
779
+
780
+ const result = await getPropertyReviews(
781
+ propertyUrl,
782
+ Math.min(maxReviews, 50)
783
+ );
784
+
785
+ return {
786
+ content: [
787
+ {
788
+ type: "text",
789
+ text: JSON.stringify(
790
+ {
791
+ success: true,
792
+ ...result,
793
+ },
794
+ null,
795
+ 2
796
+ ),
797
+ },
798
+ ],
799
+ };
800
+ }
801
+
802
+ default:
803
+ return {
804
+ content: [
805
+ {
806
+ type: "text",
807
+ text: JSON.stringify({
808
+ success: false,
809
+ error: `Unknown tool: ${name}`,
810
+ }),
811
+ },
812
+ ],
813
+ isError: true,
814
+ };
815
+ }
816
+ } catch (error) {
817
+ const errorMessage =
818
+ error instanceof Error ? error.message : String(error);
819
+
820
+ return {
821
+ content: [
822
+ {
823
+ type: "text",
824
+ text: JSON.stringify(
825
+ {
826
+ success: false,
827
+ error: errorMessage,
828
+ suggestion:
829
+ errorMessage.toLowerCase().includes("login") ||
830
+ errorMessage.toLowerCase().includes("auth") ||
831
+ errorMessage.toLowerCase().includes("sign")
832
+ ? "Try running booking_login to authenticate"
833
+ : errorMessage.toLowerCase().includes("timeout")
834
+ ? "The page took too long to load. Try again."
835
+ : errorMessage.toLowerCase().includes("search")
836
+ ? "Run booking_search first to populate results cache"
837
+ : undefined,
838
+ },
839
+ null,
840
+ 2
841
+ ),
842
+ },
843
+ ],
844
+ isError: true,
845
+ };
846
+ }
847
+ });
848
+
849
+ // Cleanup on server close
850
+ server.onclose = async () => {
851
+ await closeBrowser();
852
+ };
853
+
854
+ // Start server
855
+ async function main() {
856
+ const transport = new StdioServerTransport();
857
+ await server.connect(transport);
858
+ console.error("Strider Booking.com MCP server running");
859
+ console.error(`Config directory: ${getConfigDir()}`);
860
+ }
861
+
862
+ main().catch((error) => {
863
+ console.error("Failed to start server:", error);
864
+ process.exit(1);
865
+ });