Skip to content

agents mcp hotelbeds

Kadyapam edited this page Jul 3, 2026 · 1 revision

Agent: HotelBeds MCP

The HotelBeds MCP playbook lives at automation/agents/mcp/hotelbeds.yaml. It exposes the HotelBeds APITUDE Hotel Booking product to domain playbooks through the NoETL tool: kind: playbook MCP interface. The travel itinerary planner dispatches to it for hotel search and (TEST/SANDBOX) booking. Two sibling playbooks cover the other two APITUDE products: hotelbeds-activities.yaml and hotelbeds-transfers.yaml.

Tools

Tool Purpose
search_hotels Availability search (POST /hotel-api/1.0/hotels) for a destination + date range. Returns hotels with a bookable rateKey per rate, enriched with photo URLs from the Content API (see below).
book_hotel TEST/SANDBOX only. checkRate re-prices the rateKey (POST /hotel-api/1.0/checkrates), then confirms (POST /hotel-api/1.0/bookings). Refuses to run unless the API base is a sandbox host. Required: rate_key.
list_destinations Debug helper — resolve destination codes.
tools/list MCP discovery — lists the tools above.

Authentication

Every HotelBeds call carries three headers:

  • Api-key — the APITUDE key.
  • X-Signature — SHA256(apiKey + secret + current_unix_time_seconds) hex digest, recomputed per request (short TTL).
  • Accept-Encoding: gzip — HotelBeds requires gzip.

The key + secret resolve from the GSM secret hotelbeds-hotels-test through the worker pod's Workload Identity — they are not in the playbook or in worker env. See Execution model — secrets rule.

Photo enrichment (Content API)

HotelBeds availability responses do not carry images, so hotel cards were previously blank. search_hotels now makes one extra call to the Content API after availability:

GET /hotel-content-api/1.0/hotels?fields=images&codes=<all availability codes>

One call covers every hotel code in the result set. Each returned image path maps to the GIATA CDN:

https://photos.hotelbeds.com/giata/bigger/<path>

The URLs land on each hotel's photos array. Enrichment is fully graceful — a Content API failure leaves photos: [] and never fails the search. The travel planner's _hotel_card maps photos onto the widget card; the SPA PhotoStrip renders photos[0] with an onError placeholder (no frontend change was needed). See the travel wiki planner page.

Booking safety + price tolerance

book_hotel is guarded: _is_sandbox(base) must be true (api.test.hotelbeds.com or a sandbox marker) or the tool refuses to issue a booking — no real money can move.

Rates drift between availability and booking; checkRate is the authoritative re-price. book_hotel books the checkRate-fresh rate within a configurable tolerance:

  • workload.book_tolerance — percent, default 15 (HotelBeds' own default is 2.0, too tight for sandbox price drift).
  • If the re-price stays inside tolerance, the booking confirms and price_changed records that the amount moved.
  • If it exceeds tolerance, the tool surfaces the fresh price for the planner to re-confirm rather than silently overcharging.

Error shape — friendly user_message

_error() takes an optional user_message (mirrors the Duffel MCP hardening). The raw HotelBeds diagnostic stays in _meta.error for logs; the summary / text the planner renders become a friendly one-liner so no raw JSON blob leaks to the UI. Constants:

  • HOTELS_UNAVAILABLE_MSG — "Hotels are temporarily unavailable, please try again in a moment."
  • BOOKING_UNAVAILABLE_MSG — "Hotel booking is temporarily unavailable — please try again in a moment."

Runtime contract

Aspect Value
API base workload.hotelbeds_api_base, default https://api.test.hotelbeds.com (TEST/SANDBOX).
Secret GSM hotelbeds-hotels-test, resolved via worker Workload Identity.
Deploy Catalog registration (latest-wins), no image/worker redeploy — the Python runs in-worker.
Live catalog v5 (662611025673323404); rollback v4 (660409660586853242).

Current use

The travel itinerary planner calls search_hotels on a "hotels in …" turn and book_hotel when the user confirms a selected rate (the rate_key flows search → book through slot state). Shipped + live on prod 2026-07-03 via ops#233 (tracks noetl/ai-meta#175).

Related

Clone this wiki locally