Google Flights MCP

Pencarian penerbangan real-time: tarif sekali jalan dan pulang-pergi di berbagai rentang tanggal, dengan penilaian harga.

Dokumentasi

Travel Agent Skills

Agent skills for live flight and hotel prices. Eight skills (cheapest dates, fare watch, trip planning, destination compare, hotel search, rate-parity monitoring and more) that teach Claude, Cursor or any MCP-capable agent to answer travel price questions with real-time Google Flights fares and Booking.com rates. Every fare carries Google's own low | typical | high verdict and a bookable link, so the agent can tell someone whether a price is worth taking, not just what the price is. Works over MCP or plain REST.

Built by FlightPowers, a travel data API for developers and AI agents.

Where to get a key: you don't need one to start. Add the MCP servers below and sign in with Google: the first 10 searches a day are free, no key. After that, use your own RapidAPI key: Google Flights Live API and Booking Live API, a free pack of 10 requests a month, then PRO is $10 a month for 2,500 flight searches or 2,000 hotel searches.

Quick Links


60-Second Install

No key needed to start; sign in with Google when your client asks:

claude mcp add --transport http flights https://flights.flightpowers.com/mcp
claude mcp add --transport http hotels https://hotels.flightpowers.com/mcp

That puts four tools in your agent — one-way flights, round-trip flights, hotel search, and one named hotel — and you can ask it a real question straight away. Both servers are ad-free. Signed in, the first 10 searches a day are free with no key (one search is one date × destination combination, counted across both servers); after that your own RapidAPI key takes over. How the key works.

What comes back

Ask a flexible question and it is one call, not one per date:

Cheapest way from Berlin to Paris between 21 and 25 June 2027?
$57   Tue Jun 22   easyJet · non-stop · 1 hr 50 min · 6:15 AM    typical
$57   Wed Jun 23   easyJet · non-stop · 1 hr 50 min · 6:05 AM    typical
$57   Thu Jun 24   easyJet · non-stop · 1 hr 50 min · 6:00 AM    typical
$57   Fri Jun 25   easyJet · non-stop · 1 hr 50 min · 6:10 AM    typical
$83   Mon Jun 21   easyJet · non-stop · 1 hr 50 min · 7:00 AM    typical

Google's historical band for this route and period: $45–$105
Dates searched: Jun 21, 22, 23, 24, 25  ·  one call, five searches
Every row carries a buy_link into Google Flights for that exact itinerary.

That is an actual response from flights.flightpowers.com/mcp, captured 2026-10-23 16:46 UTC (BER → CDG, departure_date_from / departure_date_to spanning five days; the cheapest fare per date shown). It is one real search, not a benchmark — fares move constantly, and yours will differ.

The low / typical / high column is what turns a price into an answer. It comes from price_range_in_relation_to_other_periods, alongside price_insights_low and price_insights_high — Google's own price band for that route and period. It is the difference between an agent that says "$57" and one that says "$57 is typical for this route, no need to rush".


The tools your agent gets

Written for the model that has to choose between them. The parameter names below are the real ones from the tool schemas.

search_oneway_flights

Reach for this for any single-direction fare question, including open-ended ones. The destination may be a list of IATA codes and the dates may be a range (departure_date_from / departure_date_to) — the server expands both internally. "Cheapest flight to Sri Lanka anywhere in October" is one call, not thirty. Do not loop this tool over dates.

Not this one when the traveller comes back — search_roundtrip_flights prices the pair together, which is not the same number as two one-ways added up.

You get back one row per itinerary: price / price_as_number, airline, duration / duration_seconds, stops and stops_info[] (each layover airport and its duration), departure_description / arrival_description, a buy_link deep-linking that exact itinerary, and the three price-insight fields. Alongside them, search_coverage names every date actually searched.

Read search_coverage before you conclude anything. With your own key a call searches up to 30 date × destination combinations by default, and the cap rises by itself to cover a bigger request (up to 300; max_searches sets it explicitly). Past the cap a request is not rejected, it is sampled evenly across the range and comes back with truncated: true. On the free sign-in allowance a request bigger than what is left of the day's 10 searches is refused before anything runs (or, with an explicit max_searches, sampled down to what is left). A date missing from departure_dates_searched was never searched — which is not the same as having no flights.

search_roundtrip_flights

Reach for this whenever the trip returns, and especially when the user knows the trip length but not the dates: pass nights (a number, or a list like [5,6,7]) instead of return_date, with departure_date_from / departure_date_to for the outbound window. "5 to 7 nights in Rome sometime in May" is one call.

You get back total_price / total_price_as_number for both legs together, total_duration_seconds, total_stops, the outbound and return legs already paired — departure_flight_airline, return_flight_airline, per-leg stops, durations and times — and a single buy_link covering the whole trip. Same cap and same search_coverage, counted over departure-date × nights combinations.

search_hotels

Reach for this when the user wants options in a place. destination is free text the way a person says it — "Rome", "Tokyo Shibuya" — not an ID and not a coordinate, plus checkin_date and checkout_date. budget_per_night filters before you get the list.

Not this one when they have already named the property; that is find_hotel_by_name.

You get back per property: name, price_string and price, review_score and review_count, room_type, location, and a booking link.

find_hotel_by_name

Reach for this when the user names one property — "is the Hilton Paris Opera free that week" — or when you are tracking a single hotel's price over time. hotel_name is the name a person would type; adding the city helps when a chain has many properties. No internal property ID is needed; the resolution is done for you.

You get back that property's price, review_score, room_type and booking link. A property with no availability for those dates comes back as unavailable — that is an answer, not an error, and re-calling will not change it.

All four: rates and fares go stale within minutes. Never reuse a result from earlier in the conversation; search again and stamp the answer with when it was fetched.


The eight skills

The tools give your agent the data. The skills are what make it use the data well — one call for a whole date range instead of thirty, the verdict instead of a bare number, and a fresh fetch every time.

One hub holds auth, hosts, every endpoint, every response field name and the billing rules. Seven workflow skills stay thin and delegate to it.

travel-data-api                 hub — auth, hosts, endpoints, response fields, cost per call
  |- cheapest-dates             WHEN to fly: scan a date range, judge the winner
  |- destination-compare        WHERE to go: price many cities from one origin
  |- trip-planner               flights + hotel totalled into one all-in budget
  |- fare-watch                 one pinned route, re-checked over time, book-now-or-wait
  |- hotel-search               find and price places to stay
  |- rate-parity-monitor        same hotel priced as a resident of each country
  `- travel-brief               presentation layer — recommendation, table, links
SkillUse it when you want to…It returns
travel-data-apiwrite curl or client code, look up a parameter or response field, install a server, or work out what a call costsendpoint tables, runnable curl, exact JSON field names
cheapest-datesfind the cheapest day, week or month to fly a known route — or the cheapest trip lengthcheapest fare per date, the winning departure_date/return_date, buy_link, and the deal verdict
destination-comparedecide where to go: "somewhere warm under $300 in October"a price-ranked table of cities, plus a coverage line naming every destination actually searched
trip-plannerprice a named trip end to end: "7 nights in Lisbon under $2000"aligned check-in/check-out, round-trip flights, matching hotels, combined per-person total vs. the cap
fare-watchtrack one route's price over repeated checks and alert only on a meaningful droplive price, low/typical/high verdict, Google's historical band, checked_at, delta vs. your stored history
hotel-searchfind hotels by city, neighborhood or landmark, or price one named propertyname, price_string, review_score, room_type, location, link
rate-parity-monitorsee whether one named hotel is cheaper booked from another countryper-market table with the country each price was seen from, several samples per country, per-night rate in one currency, gap vs. cheapest market, and a verdict that only counts a gap that holds across the samples
travel-briefturn results into something you can send a clientrecommendation → table → verdict → booking links → checked-at line

Skills are named by the job, not by the endpoint. Two flight endpoints and two hotel endpoints produce seven workflows, because "which day is cheapest" and "where should I go" are different questions to a user and identical questions to an API.

Other prompts these are built for:

Where can I go from JFK for under $400 in late October? Somewhere warm.
Watch BER→LHR for the 14th and ping me if it drops below $90.
Plan a week in Rome in May for two, £1800 all in, hotel needs free cancellation.
Is the Hotel Gracery Shinjuku cheaper booked from Japan than from Germany, same dates?
Write up the Lisbon options for my client with a table and booking links.

Install the skills

Claude Code (plugin)

claude plugin marketplace add mtnrabi/travel-agent-skills
claude plugin install travel-agent-skills@travel-agent-skills

Restart Claude Code and all eight load together — the agent picks the right one from the question, and you can also invoke one directly by name (cheapest-dates, trip-planner, rate-parity-monitor).

Any agent with a plain skills directory

git clone https://github.com/mtnrabi/travel-agent-skills.git

# personal, every project:
cp -r travel-agent-skills/skills/* ~/.claude/skills/

# or scoped to one project:
mkdir -p .claude/skills && cp -r travel-agent-skills/skills/* .claude/skills/

Each skill is a self-contained folder with a SKILL.md and an agents/openai.yaml display descriptor, so any runtime that reads a skills directory can load them — copy the whole set, or just the folders you want. Nothing here imports anything; there is no build step and no dependency.


Your own key

The same two servers, same URLs. In a client that signs you in (Claude, Claude Code, Cursor, ChatGPT and others), open the /connect page after signing in — flights, hotels — and paste your RapidAPI key once; nothing goes in your client config. In a client without sign-in, send the key yourself:

# flights
claude mcp add --transport http flights https://flights.flightpowers.com/mcp \
  --header "x-rapidapi-key: YOUR_RAPIDAPI_KEY"

# hotels
claude mcp add --transport http hotels https://hotels.flightpowers.com/mcp \
  --header "x-rapidapi-key: YOUR_RAPIDAPI_KEY"

If your client cannot send custom headers, both also accept ?rapidapi_key= on the URL, or an API-key field where the client offers one. A key on the request always wins over a stored one. The flights server also answers on its original hostname, google-flights-mcp.flightpowers.com/mcp, so existing configs keep working.

Signed in, no keyYour own RapidAPI key
Adsnonenone
Daily budget10 searches a day per Google account, across both servers, renewing at 00:00 UTCyour own plan
Per-call search capwhat is left of the day; a bigger call is refused before it runs, or sampled down to it when max_searches is set30 by default, raised by itself for a bigger request, up to 300 via max_searches
Spend reportinga trial block with the count and the capapi_usage and remaining quota on every call
Hotelssearch and by-name, per-country pricing, 24 Booking.com filtersthe same

The hotels server takes price_as_seen_from (a two-letter country code that prices the stay as a shopper resident there would see it) and filters — free_cancellation, breakfast_included, review_score_8, stars_4, pets_allowed and nineteen more.

Past the free allowance the tools answer search_status: "trial_exhausted" with no results; with no key and no allowance they return needs_api_key: true with the signup link. Neither spends anything.


Or call the REST API directly

Both APIs are published on RapidAPI by mtnrabi and billed on your own key. The same key works for both once you subscribe to each.

APIWhat it returnsRapidAPI listing
Google Flights Live APIReal-time flight fares with Google's low|typical|high verdict, one-way and round-triprapidapi.com/mtnrabi/api/google-flights-live-api
Booking Live APIReal-time hotel availability and nightly rates with per-country pricingrapidapi.com/mtnrabi/api/booking-live-api
cp .env.example .env    # then paste your key into RAPIDAPI_KEY

The skills never print, log, or commit your key, and they will not ask for one unless a live call needs it.

There is no bulk endpoint on REST — a flexible search is a loop, and every date × destination combination is one billed request. That is exactly what the MCP range parameters exist to collapse. skills/travel-data-api/SKILL.md has the full endpoint reference, every parameter, and runnable curl.

Prefer no-code or scheduled batch runs? The same data ships as Apify actors: flights ($3.00 per 1,000 lookups) and hotels ($3.70 per 1,000 lookups).


What the data gives your agent

  • Google's own verdict, not just a number. price_insights_low, price_insights_high and price_range_in_relation_to_other_periods give the historical band for that route and period plus a low | typical | high rating. Every flight skill leads with the verdict.
  • Round-trip is a real paired-leg search, not two one-ways stapled together: one object per itinerary with a combined total, both legs already matched, and one buy_link for the trip.
  • Per-country hotel pricing. price_as_seen_from on the ad-free hotels server (proxy_country on REST) routes the request through a residential proxy in that country, so you can check the same room on the same dates as a resident of de, jp or il would be quoted it. Rates move, so sample each country a few times before you call a gap real, and expect some properties to come back priced the same everywhere. That is what rate-parity-monitor is built to do properly.
  • An empty result tells you which kind of empty it is. See below — it is the failure mode that quietly poisons a travel agent built on scraped pages.

Empty is not always empty

A flight search that was handed a page it could not read — a consent wall, a bot check, a truncated response — used to look exactly like Google saying "there are no flights on this route". Both were [] with HTTP 200. An agent renders the first as the second and confidently tells a user a route does not exist.

The REST API now says which one happened, in response headers:

HeaderMeaning
X-Search-Statusok | empty | partial | degraded
X-Search-Reasonwhat went wrong on some attempt — blocked_page, unrecognized_page, unreadable_prices, upstream_timeout, search_truncated and others. It records the first failure, so it can appear on a response a retry recovered; read X-Search-Status to decide whether you got an answer
X-Search-Results, X-Search-Attempts, X-Search-Combinationshow much work the search did
  • empty — the search completed and Google genuinely has nothing for that route and date. The empty array is the answer; retrying is a wasted request.
  • partial — real results, knowingly short. Some rows or some combinations could not be delivered.
  • degraded — the search did not complete. The empty array says nothing at all about flight availability. Retry it; nothing was booked or changed.

Unreadable pages are retried automatically, and a page that genuinely says "no flights" is never retried — so a real empty result still comes back as fast as it always did. Send "strict": true and a degraded search returns HTTP 503 with an explanatory body instead of a misleading [].

status = r.headers.get("X-Search-Status")
if status == "degraded":
    raise RuntimeError(f"search incomplete ({r.headers.get('X-Search-Reason')}), retry")
flights = r.json()
if not flights:
    print("No flights on this route for these dates")  # status == "empty"

The MCP servers read these headers for you: when part of a search did not complete, the tool result says so in words rather than handing the model a short list that looks complete.


Design rules these skills follow

  1. Prefer the MCP tools when a client has them; fall back to REST curl otherwise.
  2. Say what a call costs before making it. Every billed request is the user's money.
  3. An empty result is only "no flights" when the search says it completed. Check X-Search-Status (REST) or the tool's own outcome note (MCP) before reporting a route as empty, and never retry a genuine empty in a loop.
  4. Never cache or reuse a fare from earlier in the conversation. Always stamp the answer with when the data was fetched.
  5. Lead with the answer, then the evidence. Never present a buy_link as a held, locked, or guaranteed fare.
  6. Never print, log, or commit an API key.
  7. No invented numbers. There is no uptime, latency, user-count or revenue claim anywhere in this repo, and none should be added.

These are independent APIs that return publicly available flight and hotel pricing. They are not affiliated with, endorsed by, or sponsored by Google or Booking.com.

MIT licensed — see LICENSE.