flight-search
Search Google Flights and monitor fares on tracked routes. Use when someone asks what a flight costs, when the cheapest day to fly is, or asks to be told when a fare drops. Also covers reading the dai
SKILL.mdflight-search/skills/flight-search/SKILL.md
GitHubFlight search
Eight tools over the flights package, which scrapes Google Flights. Available
as MCP tools (search_flights, search_dates, search_flexible, plan_trip,
track_flight, check_prices, list_tracked, remove_tracked) and as a CLI at
$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py.
The one rule
An empty result is not an answer until the plugin proves the scraper is alive.
Everything here scrapes a page Google can change without notice. When it breaks, the natural symptom is an empty list, which is indistinguishable from "this route has no flights" and from "the price did not move". So the tools never hand back a bare empty result as success. You will get one of:
status: "ok"- real priced itineraries in the currency that was requestedstatus: "empty"- empty, and a live control-route query confirmed the scraper works, so the emptiness is realstatus: "error"with anerror_kind- anything else
Never translate an error into "no flights" or "no price change" when you
report to a human. That is the single failure this plugin was built to
prevent. If error_kind is scraper_error, the honest sentence is "the flight
scraper is broken, so I do not know what that fare is doing".
Searching
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" search \
--from LIS --to JFK --date 2026-09-09 --limit 5
Filters worth knowing: --adults, --children, --cabin, --max-stops,
--airlines TP,AA, --max-price, --max-duration-minutes,
--max-layover-minutes, --depart-after 6 --depart-before 20,
--arrive-before 23. --from and --to take comma-separated lists for
multi-airport searches (--from LIS,OPO --to JFK,EWR).
Flexible dates:
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" dates \
--from LIS --to JFK --from-date 2026-09-01 --to-date 2026-09-30
That returns a price per departure day plus cheapest, dearest and spread.
Use it before search whenever the dates are soft, and lead the answer with the
spread: "the 13th is 70 EUR cheaper than the 12th" is the useful sentence.
Round trips and multi-city
Same command, different legs:
# round trip
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" search \
--from LIS --to JFK --date 2026-09-19 --return-date 2026-09-26
# multi-city, legs in travel order
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" search \
--leg LIS:PHX:2026-12-20 --leg PHX:SFO:2026-12-27 --leg LAX:LIS:2027-01-06
The price on a result is the whole itinerary, not a leg. Never add the
segment prices together and never quote price_at_this_step as a fare - those
are Google's running totals through its own selection flow. If you catch yourself
about to say "and the return leg is another 490", stop: the 490 was the trip.
Multi-leg searches are slow, roughly top_n ** (legs - 1) requests. A four-leg
trip takes a minute or two. Say that before starting one rather than going quiet.
Soft dates at both ends
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" flexible \
--from LIS --to PHX --out-window 2026-12-20:2026-12-23 --back-window 2027-01-05:2027-01-08
Cheap: it sweeps trip lengths against Google's calendar grid, one request each.
Indicative prices, so follow up with search on a pair worth booking.
For a trip that has to touch several cities:
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" plan \
--from LIS --visit PHX,SFO,LAX \
--out-window 2026-12-20:2026-12-23 --back-window 2027-01-05:2027-01-08 --nights PHX=6
plan picks candidate dates with the cheap grid and then prices real multi-city
itineraries for the best few. Every price it reports is a real search; the grid
only chose the dates. Cities are visited in the order given - if the order is
worth questioning, say so and offer to re-run rather than assuming.
It takes minutes. Ask before starting one, and never schedule it.
Tracking
python3 "$FLIGHT_SEARCH_PLUGIN_DIR/scripts/flight_tools.py" track \
--from LIS --to JFK --date 2026-09-09 --target-price 400 --label "NYC in September"
--target-price is required. It is the alert condition, and without one every
wobble in a volatile fare becomes a notification. If someone asks to "watch this
flight" without naming a number, ask for one - a sensible opening move is the
current cheapest fare minus ten percent.
Round trips and multi-city trips track the same way - add --return-date or
--leg. A multi-city route re-runs its full expansion on every check, so track
those sparingly and leave top_n at its default.
Alerts fire on transitions, not states: crossing the target, dropping further below an already-alerted target, breaking, and recovering. A fare sitting below its target all week is one alert.
The daily heartbeat runs check_prices for you. Run it by hand only when
someone asks right now, and expect the run to have polled Google once already
that day.
Always hand over the link
One-way and round-trip results carry query.search_url, which re-runs the same
query on Google Flights. Include it whenever you report a price. It turns
"trust the scraper" into something the reader can settle in ten seconds, and it
is the difference between a number and a claim.
Also read query.url_caveats. It lists the filters the link cannot carry - cabin
class, passenger count, stop limits, hour windows and the rest. If it is
non-empty, say so when you hand over the link, because the browser page will not
be running the same filtered query and the numbers are allowed to differ.
Multi-city and multi-airport searches get no link, by design. For those, tell the reader to open Google Flights, switch to Multi-city and enter the legs listed in the result.
Multi-leg prices read high and you must say so. Every result carries
price_confidence. When it is indicative_reads_high there is also a
price_warning - pass its substance on, do not swallow it. Measured on
2026-08-10, a four-leg trip reported 1520 EUR while the site offered a bookable
1003 EUR fare for the same trip, a third lower. The cause is upstream and cannot
be configured away, so the right move is: give the number as a rough indication,
say a cheaper fare probably exists, and point at Google Flights.
When price_confidence is matches_site (one-way) you can state the number
plainly. It matched the site to the euro on both routes tested.
Reporting a price to a human
Lead with the number and the currency. The currency is asserted against the request, so it is trustworthy - say it explicitly anyway, because a price with no currency is a number with no meaning.
Include: price, currency, airline, stops, total duration, departure time. Skip the leg-by-leg breakdown unless asked. If the cheapest option is a fourteen-hour routing through the Azores and the next one up is three hours shorter for twenty euros more, say that - the cheapest row is not always the answer.
What this plugin does not do
It does not book, hold, pay for or cancel anything. It searches and it watches. When a fare is worth acting on, propose it and let the principal decide.