Skip to main content
Glama
flexible-hotels-mcp-server

flexible-hotels-mcp-server

by sholts2026

flexible-hotels-mcp-server

An MCP (Model Context Protocol) server that lets an AI (Claude or any other MCP client) search for hotel rooms by number of nights within a flexible date range, instead of by a fixed check-in/check-out date.

For example: "3 nights in Tel Aviv, sometime between Sep 1 and Sep 20" — the server scans all possible check-in dates in the range, fetches real prices for each one, and returns the cheapest deals sorted by price.

Affiliate model: this is a price-comparison tool only — it does not collect payment details and does not make a booking. Every result includes a link (bookingUrl) that points to the source (currently Booking.com) where the user can complete the purchase themselves, if they so choose.

Data source: Amadeus for Developers — the leading free API in the travel industry, with a completely free test environment and no credit card required.

What's here

Four tools exposed to the AI:

Tool

What it does

flexible_hotels_resolve_city_code

Converts a free-form city name ("Tel Aviv") to an IATA code ("TLV")

flexible_hotels_list_hotels_in_city

Returns a list of hotels (and their IDs) in a given city

flexible_hotels_search_flexible_offers

The main tool — flexible search by number of nights within a date range, with a booking link for each result

flexible_hotels_get_offer_details

Full details and an up-to-date price for a specific deal + booking link

The "flexible search" logic is implemented in src/services/hotelSearch.ts: since Amadeus (and any other provider) supports only one fixed date range per call, the server itself scans every possible check-in date in the defined window, calls the API accordingly, and aggregates the results. The link for each result is built in src/services/affiliateLink.ts.

Related MCP server: Amadeus Hotel API MCP Server

Installation

1. Get a free API key (2 minutes, no credit card):

  1. Register at https://developers.amadeus.com/register

  2. After verification, create a "New App" in My Self-Service Workspace

  3. You'll receive an API Key and an API Secret — these will be AMADEUS_CLIENT_ID and AMADEUS_CLIENT_SECRET

This is the test (sandbox) environment — completely free, with a generous monthly quota and a rate limit of about 10 requests per second. It covers hundreds of thousands of hotels worldwide with real data (though sometimes partial/cached). To move to full live data in the future, you can request production access from Amadeus (paid) and change AMADEUS_ENV=production.

2. Local installation:

npm install
cp .env.example .env
# ערכו את .env והכניסו את המפתחות שקיבלתם
npm run build

3. Verify everything works (without hitting the quota — sends no real requests to Amadeus):

npm test

Running

npm start

By default the server runs over stdio (suitable for local connection to clients like Claude Desktop). To run as a remote HTTP server (for connecting from Claude.ai / Cowork as a "custom connector", or from any other remote MCP client):

TRANSPORT=http PORT=3000 npm start

Connecting as an AI "plugin"

Claude Desktop (local)

Add to the claude_desktop_config.json configuration file:

{
  "mcpServers": {
    "flexible-hotels": {
      "command": "node",
      "args": ["/נתיב-מלא/ל/flexible-hotels-mcp-server/dist/index.js"],
      "env": {
        "AMADEUS_CLIENT_ID": "המפתח-שלכם",
        "AMADEUS_CLIENT_SECRET": "הסוד-שלכם"
      }
    }
  }
}

After restarting Claude Desktop, the AI will be able to call these tools directly in the conversation.

Remote connection (Claude.ai / Cowork / any other client)

To make the product available as a "plugin" to multiple users without each one running code locally, deploy the server in TRANSPORT=http mode on some server/host, then connect the address https://your-domain/mcp as a custom MCP connector in the client settings.

Railway (recommended if you already have an account + CLI installed):

cd flexible-hotels-mcp-server
railway login          # רק אם עוד לא מחוברים — פותח דפדפן לאישור
railway up --new -y --detach --json   # יוצר פרויקט חדש, מעלה את הקוד ומפעיל build מה-Dockerfile
railway variable set AMADEUS_CLIENT_ID=המפתח-שלכם --skip-deploys
railway variable set AMADEUS_CLIENT_SECRET=הסוד-שלכם --skip-deploys
railway variable set AMADEUS_ENV=test
railway domain          # מפיק כתובת ציבורית כמו https://xxx.up.railway.app

The server comes up (and passes the health check) even before the Amadeus keys are configured — the search tools will simply return a clear error until the real keys are set and an automatic redeploy happens.

Render: a render.yaml file in the project root is ready as a Blueprint — connect the repo to Render, "New" -> "Blueprint", and fill in AMADEUS_CLIENT_ID/AMADEUS_CLIENT_SECRET in the UI (see comments inside the file).

Note for all options: AMADEUS_CLIENT_ID/AMADEUS_CLIENT_SECRET must be environment variables defined on the hosting server, never written in code.

Usage example (what the AI "sees")

User: תמצא לי 3 לילות בתל אביב, איפשהו בין ה-1 ל-20 בספטמבר, לזוג.

AI calls: flexible_hotels_resolve_city_code(keyword="Tel Aviv")
  -> TLV

AI calls: flexible_hotels_search_flexible_offers(
  city_code="TLV", nights=3,
  earliest_check_in="2026-09-01", latest_check_in="2026-09-20",
  adults=2
)
  -> רשימת עסקאות ממוינת מהזולה ביותר, עם תאריך צ'ק-אין/אאוט מדויק וקישור הזמנה לכל אחת

User: מעולה, תראה לי את הפרטים המלאים של האופציה הכי זולה.

AI calls: flexible_hotels_get_offer_details(offer_id="...")
  -> מחיר מעודכן + קישור לחיצה שמעביר את המשתמש להשלים את ההזמנה באתר המקורי

The business model: affiliate

This server does not make bookings and does not touch payment details at any stage — this is intentional, both for simplicity/safety reasons and because that's how app stores (like the ChatGPT App Directory) work, which require "external checkout" and prohibit collecting credit card numbers through the tool itself.

How it works in practice:

  1. The server finds real prices through Amadeus (free).

  2. For each result, a link to a Booking.com search page is built with the hotel name and dates already filled in (src/services/affiliateLink.ts).

  3. If you sign up for the Booking.com Affiliate Partner Program (free, self-registration, no special approval) and receive an aid, you can set it as the BOOKING_AFFILIATE_ID environment variable — then every link will include your partner ID and you can earn a commission on bookings made through the link.

  4. Without BOOKING_AFFILIATE_ID the links still work fully — they're just not compensated.

This is exactly the "just need to sign up" you asked for: a one-time registration to the Booking.com partner program (not to Amadeus, not to Railway) is all that's needed for the links to start earning.

Known limitations

  • Free quota: Amadeus's test environment is limited by a monthly quota and request rate (~10/sec). A flexible search over a window of N days sends up to N requests (with a slight delay between them) — a large window = a slower search. The window is capped at 30 days per search to protect the quota.

  • Number of hotels: by default up to 15 hotels in the city are checked per date (can be raised to 30 via max_hotels, or narrowed down to specific hotelIds).

  • Sandbox data: in the test environment the most reliable coverage is in large cities (e.g. NYC, LON, PAR). Smaller cities may return few hotels or none — this is resolved by switching to production.

  • Booking links build a Booking.com search by hotel name and dates — not a direct deep link to the specific room, because Amadeus doesn't provide one. In most cases the relevant hotel will appear first in the results.

Project structure

src/
├── index.ts              # נקודת כניסה, רישום השרת והכלים
├── constants.ts           # קבועים (URLs, מגבלות)
├── types.ts                # טיפוסי TypeScript משותפים
├── services/
│   ├── amadeusClient.ts   # קליינט HTTP מאומת ל-Amadeus (OAuth2, endpoints)
│   ├── hotelSearch.ts     # הלוגיקה של חיפוש גמיש (הליבה)
│   ├── affiliateLink.ts   # בניית קישור ההזמנה (Booking.com + aid אופציונלי)
│   └── format.ts           # פורמט markdown לתשובות
├── schemas/
│   └── schemas.ts          # סכמות Zod לכל כלי
├── tools/                  # רישום כל כלי MCP
└── test/
    └── hotelSearch.test.ts # בדיקות יחידה ללוגיקת החיפוש (ללא צורך במפתח API)
scripts/
└── smoke-test-tools.mjs    # בדיקת עשן שמפעילה את השרת האמיתי ומוודאת שכל הכלים נחשפים כראוי

Possible future extensions

  • Flexible night range (min/max nights) rather than just a fixed number.

  • Caching of search results to save quota.

  • Joining additional affiliate programs (Expedia, Agoda, etc.) and showing multiple links to different sources per result, not just Booking.com.

  • Moving to a paid provider (like StayAPI / RapidAPI) for broader provider coverage as the product grows.

  • Listing in the ChatGPT app store (Apps SDK) — a separate project with its own requirements (OpenAI developer account, privacy policy, demo account for testing); the current affiliate model (without credit card collection) already complies with their "external checkout" requirement.

Available Tools

4 tools
flexible_hotels_get_offer_detailsGet Hotel Offer DetailsA
Read-onlyIdempotent

Fetch full, up-to-date details for a single hotel offer previously returned by search_flexible_hotel_offers, including its cancellation policy.

Offer prices can shift between search and booking, so call this before telling the user a final price or sending them to book. Like the rest of this server, it never collects payment details — it only reads offer details and returns a click-through link to the real site.

Args:

  • offer_id (string): the offerId returned by search_flexible_hotel_offers

  • response_format ('markdown' | 'json'): output format (default markdown)

Returns: hotel name, confirmed price, check-in/check-out dates, room description, board type, cancellation deadline, and a booking link to the real site.

Examples:

  • Use when: the user picked one result from search_flexible_hotel_offers and wants to double-check the price before going to book

  • Don't use when: you don't have an offer_id yet — run search_flexible_hotel_offers first

Error Handling:

  • Returns "No details found for this offer id" if the offer expired (Amadeus test-environment offers are short-lived)

Inputs: offer_id (required), response_format.

flexible_hotels_list_hotels_in_cityList Hotels in a CityA
Read-onlyIdempotent

List hotels (with their Amadeus hotelId) located in a given IATA city code, optionally within a radius.

Use this to discover hotelIds you can pass into search_flexible_hotel_offers via hotel_ids, e.g. to restrict a search to a specific neighborhood or a shortlist of hotels the user already mentioned by name. It does NOT return prices or availability — only hotel identity/location. This tool does not modify any data.

Args:

  • city_code (string): 3-letter IATA city code, e.g. "TLV", "PAR" (use resolve_city_code to find it)

  • radius_km (number): search radius around the city center in km, 1-300 (default 20)

  • max_results (number): max hotels to return, 1-30 (default 20)

  • response_format ('markdown' | 'json'): output format (default markdown)

Returns: hotel name, hotelId, chain code, and coordinates for each matching hotel.

Examples:

  • Use when: "only search hotels near the Tel Aviv beachfront" -> narrow radius_km, then pass returned hotelIds to search_flexible_hotel_offers

  • Don't use when: you just want priced offers — use search_flexible_hotel_offers directly, it looks up hotels automatically

Error Handling:

  • Returns "No hotels found for city code ..." if the city_code is invalid or has no coverage in this environment

Inputs: city_code (required), radius_km, max_results, response_format.

flexible_hotels_resolve_city_codeResolve City Name to IATA City CodeA
Read-onlyIdempotent

Look up the 3-letter IATA city code for a free-text city name (e.g. "Tel Aviv" -> "TLV", "Paris" -> "PAR").

Every other tool in this server (list_hotels_in_city, search_flexible_hotel_offers) requires an IATA city_code, so this is normally the first tool to call when the user names a city.

Args:

  • keyword (string): city name or the start of it, e.g. "Tel Aviv", "New York", "Par"

  • max_results (number): max cities to return, 1-20 (default 10)

  • response_format ('markdown' | 'json'): output format (default markdown)

Returns: matching cities with their name, iataCode, and country code.

Examples:

  • Use when: "find flexible hotel deals in Tel Aviv" -> call with keyword="Tel Aviv" first to get city_code="TLV"

  • Don't use when: you already have a 3-letter IATA city code

Error Handling:

  • Returns "No cities found matching ..." if the keyword doesn't match anything

Inputs: keyword (required), max_results, response_format.

flexible_hotels_search_flexible_offersSearch Hotel Offers by Number of Nights (Flexible Dates)A
Read-only

Find the cheapest hotel offers for a fixed NUMBER OF NIGHTS across a WINDOW of possible check-in dates, instead of a single fixed check-in/check-out date pair. This is the core tool of this server.

For example: "3 nights in Tel Aviv, sometime between Sep 1 and Sep 20" is expressed as nights=3, earliest_check_in="2026-09-01", latest_check_in="2026-09-20". The tool scans every possible check-in date in that window (each implying checkout = check-in + nights), fetches real priced offers for each, and returns the cheapest options found, sorted by price — so the caller can see which exact dates are the best deal.

This does NOT search by a single fixed date — for that, just set earliest_check_in = latest_check_in. This is an AFFILIATE search tool: it never collects payment details or creates a booking. Each returned offer includes a bookingUrl — a link to the real hotel/OTA listing where the guest can complete the purchase themselves, if they choose to. Data comes from the Amadeus for Developers hotel API (free test/sandbox environment by default).

Args:

  • city_code (string): 3-letter IATA city code, e.g. "TLV" (use resolve_city_code to find it from a name)

  • nights (number): exact stay length in nights, 1-28

  • earliest_check_in / latest_check_in (YYYY-MM-DD): the flexible window of possible check-in dates, at most 30 days apart

  • hotel_ids (string[], optional): restrict to specific Amadeus hotelIds (from list_hotels_in_city)

  • max_hotels (number): if hotel_ids is omitted, how many hotels in the city to auto-check (default 15)

  • adults (number): guests per room (default 2)

  • room_quantity (number): rooms to book (default 1)

  • currency (string, optional): 3-letter ISO currency code, e.g. "USD"

  • max_results (number): how many offers to return, cheapest first (default 10)

  • response_format ('markdown' | 'json'): output format (default markdown)

Returns: For JSON format: { "cityCode": string, "nights": number, "earliestCheckIn": string, "latestCheckIn": string, "datesScanned": number, "datesWithOffers": number, "datesSkipped": [{ "date": string, "reason": string }], "hotelsConsidered": number, "offers": [{ "offerId", "hotelId", "hotelName", "checkInDate", "checkOutDate", "nights", "currency", "totalPrice", "boardType", "roomDescription", "bookingUrl" }, ...], "cheapest": <same shape as one offer, or null>, "truncated": boolean }

Examples:

  • Use when: "find me the cheapest 3-night stay in Paris sometime in the next month"

  • Use when: "is it cheaper to go for a long weekend early or late September?"

  • Don't use when: the user already has exact fixed dates in mind — a single-date search is faster (set earliest_check_in = latest_check_in), though this tool still works for that case.

Error Handling:

  • Returns an error if the date window exceeds 30 days — narrow it and retry

  • Individual dates that error out (e.g. sandbox has no data) are listed in datesSkipped rather than failing the whole search

Inputs: nights (required), city_code (required), latest_check_in (required), earliest_check_in (required), +7 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.6/5.0

    Scored across 4 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: resolving city names to codes, listing hotels, searching flexible offers, and getting offer details. There is no overlap or ambiguity between them; the workflow is linear and obvious.

    Naming Consistency5/5

    All tool names follow the same pattern: 'flexible_hotels_' prefix followed by a verb_noun combination (resolve_city_code, list_hotels_in_city, search_flexible_offers, get_offer_details). The verbs are consistent and descriptive, making the API predictable.

    Tool Count4/5

    Four tools is on the lower end but well-scoped for a focused flexible hotel search server. Each tool is necessary and covers a distinct step in the workflow; there is no bloat or redundancy.

    Completeness5/5

    The server covers the complete user journey: resolve city → optionally list hotels → search offers → get details. For an affiliate-focused read-only service, the surface is complete with no obvious missing operations or dead ends.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Enables AI agents to search, browse, and book hotels from a database of 2 million properties worldwide. Provides comprehensive hotel search capabilities with location lookup, filtering by amenities, detailed property information, and integrated booking functionality.
      6
      42 npm
      1
      ISC
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables AI agents to search, compare, and book hotels with real-time pricing and availability, supporting multiple location types, star ratings, and price filters.
      12
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to search and book hotels globally with real-time pricing and inventory from over 2 million properties.
      46
      MIT