Google Flights MCP
실시간 항공편 검색: 날짜 범위에 따른 편도 및 왕복 요금, 가격 평가 포함.
문서
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
- Homepage: flightpowers.com
- Google Flights Live API: rapidapi.com/mtnrabi/api/google-flights-live-api
- Booking Live API: rapidapi.com/mtnrabi/api/booking-live-api
- GitHub: github.com/mtnrabi/travel-agent-skills
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
| Skill | Use it when you want to… | It returns |
|---|---|---|
travel-data-api | write curl or client code, look up a parameter or response field, install a server, or work out what a call costs | endpoint tables, runnable curl, exact JSON field names |
cheapest-dates | find the cheapest day, week or month to fly a known route — or the cheapest trip length | cheapest fare per date, the winning departure_date/return_date, buy_link, and the deal verdict |
destination-compare | decide 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-planner | price 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-watch | track one route's price over repeated checks and alert only on a meaningful drop | live price, low/typical/high verdict, Google's historical band, checked_at, delta vs. your stored history |
hotel-search | find hotels by city, neighborhood or landmark, or price one named property | name, price_string, review_score, room_type, location, link |
rate-parity-monitor | see whether one named hotel is cheaper booked from another country | per-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-brief | turn results into something you can send a client | recommendation → 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 key | Your own RapidAPI key | |
|---|---|---|
| Ads | none | none |
| Daily budget | 10 searches a day per Google account, across both servers, renewing at 00:00 UTC | your own plan |
| Per-call search cap | what is left of the day; a bigger call is refused before it runs, or sampled down to it when max_searches is set | 30 by default, raised by itself for a bigger request, up to 300 via max_searches |
| Spend reporting | a trial block with the count and the cap | api_usage and remaining quota on every call |
| Hotels | search and by-name, per-country pricing, 24 Booking.com filters | the 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.
| API | What it returns | RapidAPI listing |
|---|---|---|
| Google Flights Live API | Real-time flight fares with Google's low|typical|high verdict, one-way and round-trip | rapidapi.com/mtnrabi/api/google-flights-live-api |
| Booking Live API | Real-time hotel availability and nightly rates with per-country pricing | rapidapi.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_highandprice_range_in_relation_to_other_periodsgive the historical band for that route and period plus alow | typical | highrating. 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_linkfor the trip. - Per-country hotel pricing.
price_as_seen_fromon the ad-free hotels server (proxy_countryon 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 ofde,jporilwould 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 whatrate-parity-monitoris 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:
| Header | Meaning |
|---|---|
X-Search-Status | ok | empty | partial | degraded |
X-Search-Reason | what 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-Combinations | how 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
- Prefer the MCP tools when a client has them; fall back to REST curl otherwise.
- Say what a call costs before making it. Every billed request is the user's money.
- 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 genuineemptyin a loop. - Never cache or reuse a fare from earlier in the conversation. Always stamp the answer with when the data was fetched.
- Lead with the answer, then the evidence. Never present a
buy_linkas a held, locked, or guaranteed fare. - Never print, log, or commit an API key.
- 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.