Build your own client with the Gondola MCP

Last updated September 28, 2026

This guide is for developers building their own MCP client or integration on top of the Gondola MCP server, for example embedding Gondola hotel, flight, and rental car search into your own assistant. Hotel and rental car tools can hand supported options to checkout. Flight search is discovery-only and names its source; flights cannot be booked through Gondola yet. If you just want to use Gondola from Claude Code, Claude Desktop, ChatGPT, or Cursor, those clients handle all of this for you, so you do not need this page.

The Gondola MCP server is a standard OAuth 2.1 protected resource. If your client implements standard MCP discovery and the authorization-code flow with PKCE, it works out of the box. You should never hand-build or hard-code tokens.

Find the server on MCP registries

The Gondola MCP server is listed on Glama and Smithery, where you can browse its tool list and install it into supported clients.

Endpoints

PurposeURL
Interactive MCP endpointhttps://mcp.gondola.ai/mcp/auth
Public registry-discovery endpointhttps://mcp.gondola.ai/mcp
Protected-resource metadata (RFC 9728)https://mcp.gondola.ai/.well-known/oauth-protected-resource
Authorization serverhttps://www.gondola.ai
Authorization-server metadata (RFC 8414)https://www.gondola.ai/.well-known/oauth-authorization-server

Discover everything from the metadata documents rather than hard-coding paths. The authorization-server metadata advertises the authorization, token, registration, and revocation endpoints.

Every tool call requires a signed-in user

Use the interactive endpoint for end-user clients. Its first unauthenticated request returns 401 with the WWW-Authenticate challenge required to start standard MCP OAuth.

The separate registry-discovery endpoint serves initialize, notifications/initialized, ping, and tools/list anonymously, rate limited per IP, so registries and directories can list the server. Its successful discovery responses include advisory OAuth metadata, but clients must not rely on a challenge attached to a 200 response to start OAuth.

Every tools/call requires a bearer token. If you built against an earlier version that let you search without an account, that path is gone: authenticate before calling a tool. An expired or malformed token is rejected everywhere, including on discovery, so a stale credential still triggers your refresh flow rather than silently downgrading you to anonymous.

The account behind the token also has to have finished Gondola onboarding, which means either connecting an email inbox or having full site access. A valid token for an account that has not done that gets 403 with {"error": "MCP access requires completed onboarding"}. Treat 403 differently from 401: it is not a token problem, so re-running the OAuth flow will not fix it. Tell the user to finish setup at gondola.ai and try again.

How discovery works

Your client does not configure tools manually. It authenticates against the interactive endpoint and calls the standard tools/list method, which returns each tool with its name, description, and input schema. Registries can read the full public catalog from the discovery endpoint without a token. Once you authenticate, the list narrows to the tools your granted scopes allow.

Authorization flow

  1. Send any request to https://mcp.gondola.ai/mcp/auth with no or expired credentials. The server responds 401 with a WWW-Authenticate: Bearer ... resource_metadata="https://mcp.gondola.ai/.well-known/oauth-protected-resource" header.
  2. Fetch the protected-resource metadata. It returns the authorization_servers list pointing at https://www.gondola.ai.
  3. Fetch the authorization-server metadata to learn the authorization, token, registration, and revocation endpoints.
  4. Register your client with Dynamic Client Registration (RFC 7591) if you do not already have a client_id. See below.
  5. Run the authorization-code flow with PKCE (S256 is required). The user signs in to Gondola in a browser and approves the requested scopes.
  6. Exchange the code at the token endpoint for an access token, plus a refresh token if you registered for it.
  7. Send the access token as Authorization: Bearer <token> on MCP requests.

Dynamic client registration

POST https://www.gondola.ai/api/oauth/register (open registration, no auth required). Example body:

{
  "client_name": "Your App",
  "client_uri": "https://your-app.example.com",
  "redirect_uris": ["https://your-app.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

A few rules:

  • redirect_uris must be https, loopback http for local development, or Cursor's legacy DCR/fallback compatibility URI, cursor://anysphere.cursor-mcp/oauth/callback. Current Cursor clients document loopback HTTP for desktop, but may still include the custom URI during dynamic registration. Redirects exact-match at authorize time, may not contain fragments, and are limited to 10 entries.
  • Include refresh_token in grant_types to receive refresh tokens.
  • For token_endpoint_auth_method, use none for a public client (PKCE protects the exchange) or client_secret_post to receive a client_secret. The secret is shown only once at registration and stored as a hash, so capture it then.
  • The response also returns a registration_access_token. Keep it for future client management.

Scopes

ScopeGrants
mcp:readSearch, plus reading the member's own trips, favorite hotels, loyalty summaries, saved travel context, and saved searches. Granted by default when scope is omitted.
mcp:writeCreate and delete price alerts on the member's account. Must be requested and approved explicitly.
mcp:bookCharge a saved card to book, cancel a rental, and list saved payment methods and saved traveler contact details. Approved booking partners only, see below.

Request mcp:read mcp:write. That is the full surface available through the public connect flow, and it covers everything except charging a card.

mcp:book is not obtainable by registering a client and asking for it. If a client requests it, the authorization server narrows it away and issues the rest, so the connection still works. You simply won't be offered book_hotel, book_vehicle, cancel_vehicle_booking, get_payment_methods, or get_travel_profiles. Those tools are filtered out of tools/list for your connection, and calling one anyway is refused.

What read access does and does not expose

A mcp:read connection sees the member's loyalty programs, tiers, balances, point values, and expiry dates, but not their membership numbers. get_loyalty_accounts returns a selectable account ID for each program instead, and Gondola resolves the underlying number on its side when a booking needs it. Saved traveler names, email addresses, and phone numbers come only from get_travel_profiles, which requires mcp:book. Use get_traveler_context for preferences, home airport, and elite status.

get_favorite_hotels lists the hotels the member has saved as favorites, newest first (20 per page by default, 100 at most, with cursor paging). Tools that return hotels, such as search_hotels, get_hotel_rates, and compare_rates, also mark the member's favorites in their output.

update_traveler_profile is limited to Gondola's own Concierge. It does not appear in tools/list for other clients, and direct calls are rejected.

To book, use get_booking_link (hotels) or get_vehicle_booking_link (cars): they return a Gondola checkout URL for the exact rate you selected, and the traveler completes payment there. If you are integrating as a booking partner and need the in-conversation path, contact us — it is granted per client, out of band.

Rate limits

Limits are per signed-in user, not per client, so a user with two integrations shares one budget.

An overall gateway ceiling of 30 requests per minute, 200 per hour, and 500 per day applies across all tools. Exceeding it returns 429 with a Retry-After header in seconds and an X-RateLimit-Horizon header naming which window you hit (minute, hour, or day). Honour Retry-After rather than backing off on a guess, because the three windows recover on very different timescales.

Tools that reach the same paid supplier draw on the same budget, so switching between them does not buy more capacity.

Search budgets replenish. There are four, and each holds 100 units and refills continuously at 100 units per day: hotel search and comparison, dated hotel rates, vehicle search, and flight search. They are separate, so spending one does not reduce another. A full budget can be spent in one batch, for example 33 round-trip flight searches, and then recovers gradually rather than resetting at a fixed time. There are no separate per-minute or per-hour windows on these budgets; the gateway ceiling above still applies.

Searches are also limited by concurrency. Each user can have three supplier-backed searches running at once, across all of their clients. That covers hotel, vehicle, and flight searches as well as search_events and get_hotel_reviews. Up to six more wait in a queue for at most 30 seconds, and that wait counts toward the tool's overall timeout. A call that cannot get a slot, because the queue is full or the wait ran out, is refused before it spends any budget. Booking, data edits, and tools that read stored information do not join the queue.

The remaining budgets use fixed windows:

BudgetPer minutePer hourPer day
Event search630100
Hotel reviews1060200
Creating and editing data51540
Booking and cancellation136

Which tools draw on which budget:

  • Hotel search and comparison: search_hotels, compare_rates, get_similar_hotels, get_multi_night_rates, diagnose_rates
  • Dated hotel rates: get_hotel_rates. get_hotel_details returns stored property information and amenities only and does not draw on any budget; call get_hotel_rates with dates when you need prices.
  • Vehicle search: search_vehicles, get_vehicle_details
  • Flight search: search_flights returns source-attributed discovery results, cash fares, and any cached award pricing right away. It starts award lookups for the rest; get_flight_points polls for those results with the returned search_id and is free, since the supplier spend was already charged to the search. Flight booking is not available through Gondola yet.
  • Event search: search_events
  • Hotel reviews: get_hotel_reviews
  • Creating and editing data: create_rate_alert, delete_rate_alert
  • Booking and cancellation: book_hotel, book_vehicle, and cancel_vehicle_booking when confirmed

Most calls cost one unit, but a few cost more because they fan out behind the scenes. compare_rates costs one unit per hotel you pass, up to five. get_similar_hotels costs three. A search_flights call with a return date costs three because the round-trip fallback fans out; a one-way search costs one. Previewing a cancellation is free, and only confirm: true spends a booking unit.

The hotel search and comparison budget is the one most integrations hit first. Comparing five hotels and then asking for similar ones spends eight of its 100 units.

compare_rates fetches its hotels in parallel and waits up to about 15 seconds for their rate sources to finish. Each row reports the stay's own cents per point and redemption percentile, calculated from that stay's cash and points prices. A row that has not confirmed in time shows no cents per point figure rather than a program average.

When a budget is exhausted, the search queue is full, or a queued call times out, the response is still HTTP 200, and the tool result carries isError: true with a machine-readable code and retry_after_seconds inside structuredContent, along with a Retry-After header. Surface it to the user as a "try again shortly" state instead of treating it as an empty result, and wait for the stated interval before retrying.

A 503 with Retry-After means the limiter itself is briefly unavailable and the request was refused rather than risk spending supplier capacity. Retry after the interval.

If you are a partner whose integration genuinely needs more headroom, contact us rather than working around the limits with multiple accounts.

Token lifetimes and rotation

  • Access token: a signed JWT sent as a bearer token. Revocation is enforced on every request independent of the token's own expiry, so a Disconnect by the user or a re-consent takes effect within about 30 seconds regardless.
  • Refresh token: opaque, stored hashed, and rotated on every use. Each refresh returns a new refresh token and invalidates the one you presented. Always persist the newest refresh token you receive and discard the old one.
  • Authorization code: single-use, 5-minute lifetime, PKCE-bound.

Refresh-token reuse detection

Gondola rotates refresh tokens and detects reuse (RFC 6819 section 5.2.2.3). If a refresh token that has already been rotated is presented again, the server treats it as a theft signal and revokes the entire token family for that user and client. The user then has to sign in again.

What this means for you:

  • Never present a refresh token more than once. After a successful refresh, use only the new one.
  • Never replay an old refresh token to "test" it. By design that is indistinguishable from an attacker replaying a stolen token, and it will revoke the live session too.
  • If two parts of your app might refresh at the same time, serialize refreshes so you never present the same token twice.

Re-authentication

When a refresh fails because the family was revoked (the user disconnected, reuse was detected, or a re-consent happened), there is no silent recovery. Start a fresh authorization-code flow and have the user sign in again. Surface a clear "reconnect" action rather than failing silently.

Security do's and don'ts

  • Do store tokens in OS-level secure storage (Keychain, Credential Manager, and the like), never in logs or chat transcripts.
  • Do persist and rotate to the newest refresh token on every refresh.
  • Do handle 401 with WWW-Authenticate by driving the standard flow, not by asking the user to paste a token.
  • Don't retry the OAuth flow on a 403. The token is fine and the account has not finished onboarding, so a retry loop just sends the user through sign-in repeatedly.
  • Don't retry a 429 immediately. Wait for Retry-After, and check X-RateLimit-Horizon before assuming a short pause is enough.
  • Don't print access or refresh tokens into any model context or chat. If a token is ever exposed, the user can revoke it instantly at Connected Agents.
  • Don't replay or reuse a rotated refresh token.

Questions

Reach out to the Gondola team. We are happy to help partners land a clean integration before they rely on it in production.