flexible-hotels-mcp-server
flexible-hotels-mCP-server
AI(Claude 또는 기타 MCP 클라이언트)가 고정된 체크인/체크아웃 날짜 대신 유연한 날짜 범위 내의 숙박 일수 기준으로 호텔 객실을 검색할 수 있게 해주는 MCP(Model Context Protocol) 서버입니다.
예: "텔아비브에서 3박, 9월 1일에서 9월 20일 사이 아무 때나" — 서버는 범위 내 모든 가능한 체크인 날짜를 스캔하고, 각 날짜의 실제 가격을 가져와 가격순으로 정렬된 가장 저렴한 거래를 반환합니다.
제휴(Affiliate) 모델: 이 도구는 가격 비교 도구일 뿐입니다 — 결제 정보를 수집하지 않으며 예약을 수행하지 않습니다. 각 결과에는 사용자가 직접 구매를 완료할 수 있는 소스(현재는 Booking.com)로 연결되는 링크(bookingUrl)가 포함되어 있습니다.
데이터 소스: Amadeus for Developers — 관광 분야 최고의 무료 API로, 완전히 무료이며 신용카드가 필요 없는 테스트(test) 환경을 제공합니다.
구성 요소
AI에 노출되는 4가지 도구(tools):
도구 | 기능 |
| 자유 형식 도시 이름("텔아비브")을 IATA 코드("TLV")로 변환 |
| 지정된 도시의 호텔 목록(및 ID) 반환 |
| 핵심 도구 — 날짜 범위 내 숙박 일수 기준 유연한 검색, 각 결과에 예약 링크 포함 |
| 특정 거래의 전체 세부 정보 및 최신 가격 + 예약 링크 |
"유연한 검색" 로직은 src/services/hotelSearch.ts에 구현되어 있습니다: Amadeus(및 기타 모든 공급업체)는 호출당 고정된 날짜 범위 하나만 지원하므로, 서버가 정의된 창(window) 내의 모든 가능한 체크인 날짜를 직접 스캔하고 그에 따라 API를 호출한 후 결과를 집계합니다. 각 결과에 대한 링크는 src/services/affiliateLink.ts에서 생성됩니다.
Related MCP server: Amadeus Hotel API MCP Server
설치
1. 무료 API 키 받기 (2분, 신용카드 불필요):
인증 후 My Self-Service Workspace에서 "New App" 생성
API Key와API Secret을 받게 됩니다 — 이것이AMADEUS_CLIENT_ID및AMADEUS_CLIENT_SECRET입니다
이것은 test(샌드박스) 환경입니다 — 완전히 무료이며, 넉넉한 월별 할당량과 초당 약 10회의 요청 속도 제한이 있습니다. 전 세계 수십만 개 호텔의 실제 데이터(때로는 부분적/캐시된 데이터)를 포함합니다. 향후 완전한 실시간 데이터로 전환하려면 Amadeus에 production 액세스를 요청(유료)하고 AMADEUS_ENV=production으로 변경하면 됩니다.
2. 로컬 설치:
npm install
cp .env.example .env
# ערכו את .env והכניסו את המפתחות שקיבלתם
npm run build3. 모든 것이 작동하는지 확인 (할당량 소진 없이 — Amadeus에 실제 요청을 보내지 않음):
npm test실행
npm start기본적으로 서버는 stdio를 통해 실행됩니다(Claude Desktop과 같은 클라이언트에 로컬 연결에 적합). HTTP를 통한 원격 서버로 실행하려면(Claude.ai / Cowork에서 "custom connector"로 연결하거나 다른 원격 MCP 클라이언트에서 연결):
TRANSPORT=http PORT=3000 npm startAI "플러그인"으로 연결
Claude Desktop (로컬)
claude_desktop_config.json 설정 파일에 추가:
{
"mcpServers": {
"flexible-hotels": {
"command": "node",
"args": ["/נתיב-מלא/ל/flexible-hotels-mcp-server/dist/index.js"],
"env": {
"AMADEUS_CLIENT_ID": "המפתח-שלכם",
"AMADEUS_CLIENT_SECRET": "הסוד-שלכם"
}
}
}
}Claude Desktop을 재시작하면 AI가 대화에서 이러한 도구를 직접 호출할 수 있습니다.
원격 연결 (Claude.ai / Cowork / 기타 클라이언트)
제품을 여러 사용자가 각자 로컬에서 코드를 실행하지 않고 "플러그인"으로 사용할 수 있게 하려면, 서버를 TRANSPORT=http 모드로 서버/호스트에 배포(deploy)한 다음 https://your-domain/mcp 주소를 클라이언트 설정에서 custom MCP connector로 연결합니다.
Railway (이미 계정 + CLI가 설치된 경우 권장):
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서버는 Amadeus 키를 설정하기 전에도 시작됩니다(그리고 health check를 통과합니다) — 검색 도구는 실제 키가 설정되고 자동 재배포가 이루어질 때까지 명확한 오류를 반환할 뿐입니다.
Render: 프로젝트 루트의 render.yaml 파일이 Blueprint로 준비되어 있습니다 — repo를 Render에 연결하고, "New" -> "Blueprint"를 선택한 후 인터페이스에서 AMADEUS_CLIENT_ID/AMADEUS_CLIENT_SECRET을 입력합니다(파일 내 주석 참조).
모든 옵션에서 주의: AMADEUS_CLIENT_ID/AMADEUS_CLIENT_SECRET은 호스팅 서버에 정의된 환경 변수여야 하며, 코드에 절대 하드코딩하지 마세요.
사용 예시 (AI가 "보는" 것)
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="...")
-> מחיר מעודכן + קישור לחיצה שמעביר את המשתמש להשלים את ההזמנה באתר המקורי비즈니스 모델: 제휴(Affiliate)
이 서버는 예약을 수행하지 않으며 어떤 단계에서도 결제 정보를 다루지 않습니다 — 이는 의도적인 설계로, 단순성/안전성 때문이기도 하고 "external checkout"을 요구하고 도구 자체를 통한 신용카드 번호 수집을 금지하는 앱 스토어(예: ChatGPT App Directory)의 방식이기도 합니다.
실제 작동 방식:
서버는 Amadeus(무료)를 통해 실제 가격을 찾습니다.
각 결과에 대해 호텔 이름과 날짜가 미리 채워진 Booking.com 검색 페이지 링크가 생성됩니다(
src/services/affiliateLink.ts).Booking.com Affiliate Partner Program(무료, 자체 가입, 특별 승인 불필요)에 등록하고
aid를 받으면, 이를 환경 변수BOOKING_AFFILIATE_ID로 설정할 수 있습니다 — 그러면 모든 링크에 파트너 ID가 포함되어 링크를 통한 예약에 대해 커미션을 받을 수 있습니다.BOOKING_AFFILIATE_ID가 없어도 링크는 완전히 작동합니다 — 단지 보상이 없을 뿐입니다.
이것이 바로 요청하신 "그냥 등록만 하면 되는" 것입니다: Booking.com 파트너 프로그램에 일회성 등록(Amadeus나 Railway가 아닌)만 하면 링크가 수익을 창출하기 시작합니다.
알려진 제한 사항
무료 할당량: Amadeus
test환경은 월별 할당량과 요청 속도(~10/초)가 제한됩니다. N일 창에 대한 유연한 검색은 최대 N개의 요청을 보냅니다(요청 사이에 약간의 지연 포함) — 창이 클수록 검색이 느려집니다. 할당량 보호를 위해 창은 검색당 30일로 제한됩니다.호텔 수: 기본적으로 날짜별로 도시에서 최대 15개 호텔이 확인됩니다(
max_hotels를 통해 최대 30개까지 늘리거나 특정hotelIds로 줄일 수 있음).샌드박스 데이터:
test환경에서 가장 안정적인 커버리지는 대도시(예: NYC, LON, PAR)입니다. 더 작은 도시는 호텔이 거의 없거나 전혀 반환되지 않을 수 있습니다 —production으로 전환하면 해결됩니다.예약 링크는 호텔 이름과 날짜를 기준으로 Booking.com 검색을 구성합니다 — 특정 객실에 대한 직접 딥 링크가 아닙니다. Amadeus가 이를 제공하지 않기 때문입니다. 대부분의 경우 관련 호텔이 결과에서 첫 번째로 나타납니다.
프로젝트 구조
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 # בדיקת עשן שמפעילה את השרת האמיתי ומוודאת שכל הכלים נחשפים כראוי향후 확장 가능성
고정 일수 대신 유연한 숙박 일수 범위(min/max nights).
할당량 절약을 위한 검색 결과 캐싱.
추가 제휴 프로그램(Expedia, Agoda 등) 가입 및 각 결과에 대해 Booking.com뿐만 아니라 여러 소스 링크 표시.
제품이 성장함에 따라 더 넓은 공급업체 커버리지를 위한 유료 제공업체(StayAPI / RapidAPI 등)로 전환.
ChatGPT 앱 스토어(Apps SDK) 등록 — 자체 요구 사항(OpenAI 개발자 계정, 개인정보 처리방침, 테스트용 데모 계정)이 있는 별도 프로젝트; 현재 제휴 모델(신용카드 수집 없음)은 이미 "external checkout" 요구 사항을 충족합니다.
Available Tools
4 toolsflexible_hotels_get_offer_detailsGet Hotel Offer DetailsARead-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)
| Name | Required | Description | Default |
|---|---|---|---|
| offer_id | Yes | The Amadeus offer id returned by search_flexible_hotel_offers, e.g. 'XYZ123' | |
| response_format | No | Output format: 'markdown' for human-readable or 'json' for machine-readable | markdown |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover readOnlyHint, openWorldHint, idempotentHint, and destructiveHint. The description goes beyond these by disclosing that prices may shift, that it never collects payment details, that it returns a click-through link, and that expired offers produce a specific error message. This provides rich behavioral context that annotations alone do not offer, and it does not contradict any annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear sections (main description, Args, Returns, Examples, Error Handling) and is front-loaded with the core purpose. It is somewhat repetitive of the input schema (the Args section duplicates schema descriptions), but each section carries useful information and there is no fluff. A slightly tighter version could omit the repetition, but overall it is appropriately concise for the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by listing the exact fields returned (hotel name, price, dates, room, board, cancellation deadline, booking link). It also covers error handling and usage context. For a single-offer detail tool with read-only semantics, nothing essential is missing for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already fully documents both offer_id and response_format, including their types, defaults, and descriptions. The description repeats the same info (e.g., 'offerId returned by search_flexible_hotel_offers') without adding new meaning beyond what the schema provides. The expiry note appears in error handling, not in parameter semantics, so the description adds marginal value over the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('fetch'), a precise resource ('full, up-to-date details for a single hotel offer previously returned by search_flexible_hotel_offers'), and explicitly ties it to a sibling search tool. This clearly differentiates it from the siblings (resolve_city_code, list_hotels_in_city, search_flexible_offers), so an agent knows exactly what this tool does and when to use it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit 'Use when' and 'Don't use when' examples, including a concrete scenario (double-check price before booking) and a negative condition (no offer_id yet -> run search first). It also states the timing requirement ('call this before telling the user a final price or sending them to book'), leaving no ambiguity about when to invoke this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
flexible_hotels_list_hotels_in_cityList Hotels in a CityARead-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
| Name | Required | Description | Default |
|---|---|---|---|
| city_code | Yes | IATA city code, e.g. 'TLV' for Tel Aviv, 'PAR' for Paris (use resolve_city_code to find it) | |
| radius_km | No | Search radius around the city center, in kilometers | |
| max_results | No | Maximum hotels to return | |
| response_format | No | Output format: 'markdown' for human-readable or 'json' for machine-readable | markdown |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint=true, idempotentHint=true, destructiveHint=false), so the bar is lower. The description adds genuine value beyond annotations: it discloses that the tool does NOT return prices/availability, states 'This tool does not modify any data' (consistent with destructiveHint=false), and documents the exact error message for invalid city codes. There is no contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized with clear section headers (Args, Returns, Examples, Error Handling) and is front-loaded with the core purpose. Every section earns its place — the examples, error handling, and return description all add operational value. Slightly longer than minimal, but the structure makes it scannable, and no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple (4 params, 1 required, all schema-documented, no nested objects, no output schema) with rich annotations. The description covers what's returned (hotel name, hotelId, chain code, coordinates), error handling, the chaining pattern with search_flexible_hotel_offers, and when not to use it. An agent has everything needed to call it correctly — including awareness that no output schema exists but the return content is described.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3. The description largely mirrors the schema (e.g., radius range 1-300, default 20, response_format enum). It adds modest value by embedding usage context in the examples (e.g., 'narrow radius_km' for a beachfront search, 'use resolve_city_code to find it' — though that hint also exists in the schema). The description doesn't introduce meaning beyond what the schema provides, so 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb-resource-scope combination: 'List hotels (with their Amadeus hotelId) located in a given IATA city code, optionally within a radius.' It explicitly differentiates itself from search_flexible_hotel_offers by clarifying it returns only hotel identity/location, NOT prices or availability. An agent can distinguish this from all three siblings without opening their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description names the exact alternative (search_flexible_hotel_offers), the chaining pattern (pass returned hotelIds into it), and provides concrete 'Use when' / 'Don't use when' examples. It even shows how to narrow radius_km for neighborhood-specific searches. Nothing about when to invoke this tool is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
flexible_hotels_resolve_city_codeResolve City Name to IATA City CodeARead-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
| Name | Required | Description | Default |
|---|---|---|---|
| keyword | Yes | Free-text city name or the start of it, e.g. 'Tel Aviv', 'Par', 'New York' | |
| max_results | No | Maximum cities to return | |
| response_format | No | Output format: 'markdown' for human-readable or 'json' for machine-readable | markdown |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false. The description adds beyond these by specifying the return format (name, iataCode, country code), the exact error message ('No cities found matching ...'), and clarifies it is a pure lookup with no side effects. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured and front-loaded with purpose and usage context. It includes a clear one-liner, a brief explanation of why it's used first, parameter descriptions (somewhat redundant with schema), returns, examples, and error handling. It is slightly lengthy due to repeating the Args section, but every section earns its place and the structure aids comprehension.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple lookup tool with three parameters, no output schema, and strong annotation coverage, this description covers all necessary aspects: purpose, when to use, examples, return format, and error handling. An agent can invoke it correctly without ambiguity, making it complete for its complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with all three parameters fully documented. The tool description repeats parameter details (e.g., max_results range and default, response_format enum) but adds no new semantic meaning beyond what the schema already provides. It reinforces usage examples but does not introduce additional information, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: 'Look up the 3-letter IATA city code for a free-text city name,' with concrete examples (Tel Aviv -> TLV, Paris -> PAR). It clearly distinguishes itself from sibling tools by explaining that all other tools require an IATA city_code, making this the prerequisite lookup tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use it ('normally the first tool to call when the user names a city') and when not to use it ('Don't use when: you already have a 3-letter IATA city code'). It names dependent tools (list_hotels_in_city, search_flexible_hotel_offers) and provides a concrete example of usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
flexible_hotels_search_flexible_offersSearch Hotel Offers by Number of Nights (Flexible Dates)ARead-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
| Name | Required | Description | Default |
|---|---|---|---|
| adults | No | Number of adult guests per room | |
| nights | Yes | Exact number of consecutive nights the guest wants to stay, e.g. 3 | |
| currency | No | Optional 3-letter ISO currency code to request prices in, e.g. 'USD', 'EUR', 'ILS' | |
| city_code | Yes | IATA city code to search in, e.g. 'TLV' for Tel Aviv (use resolve_city_code to find it from a name) | |
| hotel_ids | No | Optional list of specific Amadeus hotelIds to restrict the search to (from list_hotels_in_city). If omitted, the tool automatically looks up hotels in city_code and checks up to max_hotels of them. | |
| max_hotels | No | When hotel_ids is not provided, how many hotels in the city to check (higher = more thorough but slower, capped at 30) | |
| max_results | No | Maximum number of offers to return, sorted cheapest first | |
| room_quantity | No | Number of rooms to book | |
| latest_check_in | Yes | Latest allowed check-in date, YYYY-MM-DD. Must be no more than 30 days after earliest_check_in. | |
| response_format | No | Output format: 'markdown' for human-readable or 'json' for machine-readable | markdown |
| earliest_check_in | Yes | Earliest allowed check-in date, YYYY-MM-DD. The search scans every possible check-in date from this date through latest_check_in. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
While annotations already declare readOnlyHint and destructiveHint=false, the description enriches the behavioral profile substantially. It discloses an affiliate-search model ('never collects payment details or creates a booking'), the external bookingUrl flow, the Amadeus sandbox data source, per-date error handling via datesSkipped, and the scanning of every check-in date. These details go well beyond the annotations and materially shape an agent's expectations for side effects and response quirks. No contradiction with annotations; readOnlyHint aligns with the stated no-booking behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but unusually well-organized. It front-loads the core concept and the key differentiator (flexible window vs fixed dates) in the opening sentences, then flows logically through an example, non-use cases, affiliate disclaimer, parameter list, return shape, usage examples, and error handling. Every section earns its place: the return JSON is especially valuable given the lack of an output schema, and the error-handling note covers edge cases an agent would otherwise discover only at runtime. No redundant filler; the length is justified by the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter tool with no output schema, the description is exceptionally complete. It specifies the exact JSON return structure, including datesSkipped with reason fields and the cheapest summary object, which an agent needs to parse results reliably. It covers error conditions (window >30 days, per-date failures), explains the scanning algorithm, and provides two representative use cases. Combined with full schema parameter descriptions and annotations, nothing required to correctly invoke and interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so each parameter is already documented. The description adds value by explaining the conceptual relationship between earliest_check_in and latest_check_in as a 'window' with a worked example ('3 nights in Tel Aviv... is expressed as nights=3, earliest_check_in="2026-09-01", latest_check_in="2026-09-20"'), and by clarifying default behaviors (max_hotels auto-check, response_format selection). These additions go beyond simple schema repetition, though the marginal gain is modest since the schema descriptions already cover the same facts. A solid 4, not a 5 because the added insight is illustrative rather than essential.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise statement: 'Find the cheapest hotel offers for a fixed NUMBER OF NIGHTS across a WINDOW of possible check-in dates.' It specifies the resource (hotel offers), the action (find cheapest), and the distinctive scope (flexible dates rather than fixed). It also explicitly positions itself as 'the core tool of this server' and contrasts with sibling tools such as list_hotels_in_city and resolve_city_code, making its role unmistakable. No ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is explicit and actionable. It provides concrete when-to-use examples ('find me the cheapest 3-night stay in Paris sometime in the next month') and a clear don't-use case ('Don't use when: the user already has exact fixed dates in mind'), followed by the recommended alternative (set earliest_check_in = latest_check_in). It also points to companion tools (resolve_city_code, list_hotels_in_city) for prerequisites, making routing between siblings trivial.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v1.0.0- First observed
flexible_hotels_get_offer_details - First observed
flexible_hotels_list_hotels_in_city - First observed
flexible_hotels_resolve_city_code - First observed
flexible_hotels_search_flexible_offers
TDQS
Scored across 4 tools
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.
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.
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.
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
Related MCP Connectors
Search and book 3M+ hotels in 200+ countries. Agents pay per reservation in USDC on Base over x402. Hosted, no auth.
Book real hotel and resort stays from any AI agent: all-in pricing, hosted Stripe checkout, no key.
Search hotel prices, get best overall and best direct price in structured response. Get your developer token at https://Infoseek.ai/mcp
Search MCP servers, agent skills and APIs for travel: hotel ID mapping, rates, rooms.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables 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.69 npm1ISC
- FlicenseAqualityDmaintenanceEnables AI assistants to search for and book hotels via the Amadeus Travel API, providing hotel listings, offers, and booking capabilities.41-

DIDA Hotel MCPofficial
AlicenseNot gradedqualityBmaintenanceEnables AI agents to search, compare, and book hotels with real-time pricing and availability, supporting multiple location types, star ratings, and price filters.11MIT
Dida Hotel MCPofficial
AlicenseNot gradedqualityDmaintenanceEnables AI agents to search and book hotels globally with real-time pricing and inventory from over 2 million properties.46MIT