flights-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@flights-mcpfind the cheapest flights from Zurich to Barcelona next week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
flights
A local MCP server for planning a trip from live public fares.
It speaks stdio, needs no API key, and reads Google Flights pages through the
unofficial fast-flights query builder.
Airport names are resolved offline with airportsdata.
Defaults are 1 adult, economy, CHF.
Why this source
The constraint is a planner an agent can run with no paid account and no API key, and with no invented prices. The fields that matter are flight numbers, layovers, bags or fare family, a date window, and a fare that was actually returned.
Source | What it would take | What a planner gets |
Google Flights page, parsed here | No key. Unofficial, so it can break or be blocked. | Live CHF prices. Flight numbers and layover minutes are in the page payload. Bags and fare family are not attached to an itinerary. No date-price calendar, so a window is several bounded searches. |
Kiwi Tequila |
| Not usable. |
Amadeus, Duffel, Skyscanner partner APIs | A developer account and a key. | Out of scope. |
A single airline API | No key for some public pages, but only that airline. | Misses the rest of a ZRH–BCN comparison. |
Hosted date-grid sites (for example whentofly) | No key, but the fares are a hosted cache, not this process reading a live public page. | Not a local source, and not something to treat as a bookable fare. |
Google is the one that still lets an agent compare real itineraries. The server says so when a field is missing. It does not fill bags in from legroom inches or from unnamed numeric flags on the payload.
Related MCP server: rihla
Run
From this directory, with uv:
uv sync
uv run flights-mcpThe same process is uv run python -m flights_mcp. A host that does not use
uv can launch the module with whatever Python has the package installed:
python -m flights_mcpThe process is silent on stdout. MCP messages are the stdout stream. Logs go to stderr.
Example host config (Cursor, Claude Desktop, and anything else that launches a local stdio server):
{
"mcpServers": {
"flights": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/this/repo", "flights-mcp"]
}
}
}No environment variables are required.
Tools
search_flights
Search live public fares across a short set of departure dates.
Field | Required | Default | Bounds |
| yes | IATA code or city name, 1–80 characters | |
| yes | same | |
| one of the date forms |
| |
| no |
| 0–3 days either side of |
| with | first departure, inclusive | |
| with | last departure, inclusive | |
| no | one-way | fixed return, on or after a searched departure |
| no | 1–30. Return becomes each departure plus this many days. Not with | |
| no |
| 1–9 |
| no |
|
|
| no |
| a currency Google Flights prices in ( |
| no |
| 1–20 |
| no |
| direct flights only |
Pass depart_date, or depart_date with flexibility_days, or
depart_from and depart_to. Not both an anchor and a from/to window.
"Zurich to Barcelona, sometime in a week" is depart_from and depart_to
seven days apart.
At most 7 departure dates are queried, one after another, inside 25
seconds. A longer window is sampled, including the first and last day, and
the dates that were skipped are listed in dates_not_searched. A day that
runs out of time is recorded on days and is not given an invented fare.
nights does not search a grid of return dates. Each departure is one query.
Each itinerary includes:
price and currency, as the integer Google returned
airline names, local times, duration, stops, route, and each leg
flight_numberswhen Google sent them. A leg without one hasflight_number: nulland a note that says solayovers: airport and minutes.
minutes_basisisgooglewhen the payload included the layover length,schedulewhen it was the gap between the leg times, andunknownwhen neither could be read. An emptylayoverslist means nonstopcarry_on,checked_bag, andfare_family. On this source those areunknown/null, andfare_notesays that nothing was assumedsearch_urlfor that date, andbooking_urlwhen Google attached a booking token. The booking link opens the itinerary on Google Flights. It is not a ticket
picks.cheapest, picks.fastest, and picks.compromise are itinerary ids.
compromise is the option with the best even balance of price and duration
among the flights that are not already the cheapest or the fastest. When the
pool is too small for a distinct third option, it matches one of the other
picks. The same id can carry more than one label. flights is sorted by price
and always includes the three picks, up to max_results.
A city that matches several airports (Springfield) is an error. Pass an IATA
code, or a metro code such as NYC. London and New York resolve to the
metro code so the search covers the city's airports.
Unknown arguments are rejected. A failure is a tool error whose text contains
JSON {"code","message",...}. Codes include unknown_place,
ambiguous_place, invalid_input, no_results, unpriced, timeout, and
upstream. An error never carries an invented price. A single day inside a
window can also be skipped when its return would fall before departure.
resolve_airports
Offline IATA lookup.
Field | Required | Default | Bounds |
| yes | city, airport name, or IATA code | |
| no |
| 1–20 |
Zurich and Zürich resolve to ZRH. Barcelona resolves to BCN (Barcelona
in Venezuela is BLA; the airport named for the city is the one a search
uses). NYC returns the metro code plus JFK and LGA. Newark is EWR.
Limits
Fares are snapshots. They can change before anyone books.
Bags and fare family are not on the itinerary Google returns to this parser. The fields are present so a planner can see the absence. They are never guessed.
Times are local to each airport and have no offset.
duration_basisisgooglewhen the payload included the trip duration,elapsedwhen it was computed from airport timezones, andairwhen only airborne minutes were available.For a round trip,
priceis the total Google showed for that departure and return.legsare the segments included in that result.The price is the integer Google returned, in the currency you asked for, in major units. It is not converted or rounded. Itineraries with no usable price are omitted. If none remain, the tool errors with
unpriced.Identical searches in one process are reused for 2 minutes. The cache is memory only and includes the date window.
Each day contributes at most 4 itineraries to the comparison: the cheapest ones, plus that day's fastest if it was not already among them. The overall list is still capped by
max_results, and the labeled options are kept inside that cap.The whole search, including every date in the window, gets 25 seconds.
At most two searches run at once.
Read SECURITY.md before pointing anything automated at this.
fast-flights can break when Google changes the page, it can be rate
limited, and automated access may violate Google's terms.
Tests
Offline tests cover schemas, validation, airport resolution, the page parser, date-window sampling, and a search that never leaves the machine:
uv run pytestThe live check is opt-in. It searches ZRH to BCN across seven days, one adult, economy, CHF, a few weeks out:
FLIGHTS_LIVE=1 uv run pytest tests/test_live.py -sLive check
FLIGHTS_LIVE=1 uv run pytest tests/test_live.py on 2026-09-30. One-way
ZRH to BCN, departures 2026-10-26 through 2026-11-01 (seven days, all
searched), 1 adult, economy, CHF. The call wrote nothing to stdout. Every
priced itinerary had carry_on and checked_bag set to unknown and
fare_family null. Nothing was filled in.
Cheapest fare Google returned for each day:
Departure | Cheapest |
2026-10-26 | 63 CHF |
2026-10-27 | 48 CHF |
2026-10-28 | 57 CHF |
2026-10-29 | 63 CHF |
2026-10-30 | 69 CHF |
2026-10-31 | 57 CHF |
2026-11-01 | 54 CHF |
The eight itineraries the tool returned (max_results 8), sorted by price.
picks were the 48 CHF Vueling, the 54 CHF Vueling, and the 75 CHF SWISS.
Price | Date | Flight | Local times | Duration | Stops | Codeshare |
48 CHF | 27 Oct | VY1229 | 21:15 → 23:10 | 1h 55m | 0 | IB5489 |
54 CHF | 1 Nov | VY1227 | 09:40 → 11:30 | 1h 50m | 0 | IB5487 |
57 CHF | 27 Oct | VY1227 | 09:40 → 11:30 | 1h 50m | 0 | IB5487 |
57 CHF | 28 Oct | VY1227 | 09:40 → 11:30 | 1h 50m | 0 | IB5487 |
57 CHF | 28 Oct | VY1229 | 21:35 → 23:30 | 1h 55m | 0 | IB5489 |
57 CHF | 31 Oct | VY1229 | 21:15 → 23:10 | 1h 55m | 0 | IB5489 |
63 CHF | 26 Oct | VY1227 | 09:40 → 11:30 | 1h 50m | 0 | IB5487 |
75 CHF | 1 Nov | LX1954 | 12:20 → 14:05 | 1h 45m | 0 | none |
Duration basis on these was google. The cheapest Vueling was an Airbus
A320, with the carbon figure Google sent (83000 grams) and a booking URL.
Fetching that booking URL from this environment returned HTTP 200 and the
Google Flights page. It is not a held ticket. The booking token is not
copied here.
The same parser, on the 2026-10-28 page alone, also saw connections. They did not make the shortlist: the nonstops above were cheaper, and the SWISS flight was faster, so the per-day keep (cheapest plus fastest) never held onto them. They are recorded because the layover fields were present:
Price | Flights | Duration | Layover |
77 CHF | JU331, JU580 | 23h 40m | Belgrade (BEG), 1155 minutes, as Google reported |
101 CHF | AF1815, AF1448 | 4h 5m | Paris (CDG), 45 minutes |
138 CHF | SN2732, SN3703 | 4h 25m | Brussels (BRU), 70 minutes |
Bags and fare family were absent on those connections too.
Search URL for the first day of the window:
Treat the tables as a snapshot from that run.
Layout
flights_mcp/server.py— MCP tools and stdio entrypointflights_mcp/search.py— date window, labels, timeout, cacheflights_mcp/google_parse.py— flight numbers, layovers, and prices from the pageflights_mcp/dates.py— the 7-date capflights_mcp/upstream.py— one bounded Google Flights fetchflights_mcp/airports.py— local IATA lookupflights_mcp/models.py— strict input and output schemas
This server cannot be deployed
Maintenance
Related MCP Connectors
Search and compare flight offers through a cache-aware Streamable HTTP MCP server for AI agents.
Flight search & booking for AI agents. 400+ airlines, $20-50 cheaper than OTAs.
Live flight prices and working booking links for AI agents and travel apps.
Real-time Google Flights fares for agents. Three things people do with this server. Scan for deals: one call takes a date range and a list of destination airports, expands every combination server side, and returns each fare with Google's own low, typical or high verdict. Put live search in your app: flat JSON with a bookable link on every result, and round trips priced as paired legs. Run a 24/7 AI travel agent: add the server, sign in with Google, and schedule it. No ads, no sponsored content. You bring your own RapidAPI key, so every search is billed to your plan and never to anyone else's. Add https://flights.flightpowers.com/mcp , click Sign in, sign in with Google, and paste your RapidAPI key once on the page that opens. Scripts and clients without a sign-in button send the key as x-rapidapi-key on the same URL.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceFlight search for AI agents: flexible-date cheapest round-trips with a good-price verdict from historical data. Hosted remote MCP, free, no key.3MIT
- AlicenseNot gradedqualityCmaintenanceFlexible multi-leg, multi-airport flight search that finds the cheapest route across an entire itinerary — available as a CLI and as an MCP server for AI agents.Apache 2.0
- AlicenseAqualityBmaintenanceEnables users to find the cheapest dates to fly a route via Google Flights, supporting one-way and round-trip searches through multiple backends. It provides a tool that can search date ranges, filter by nonstop, seat, currency, and force a particular backend.1MIT
- AlicenseAqualityBmaintenanceEnables real-time flight fare searches across date ranges and multiple destinations, with historical price insights and booking links. Provides one-way and round-trip search tools through MCP.41MIT