Model or dataset
punitarani/fli avatar
punitarani/fli

fli: a Google Flights client that talks to the API instead of scraping HTML

Google Flights MCP, CLI and Python Library

3,155 stars395 forksPythonMIT

At a glance

What is it?
punitarani/fli wraps Google Flights behind an MCP server, a CLI and a Python package. The README claims direct API interaction through reverse engineering, which is the whole bet: less breakage than scraping, but a dependency on an undocumented endpoint.
Who is it for?
Adopt fli if you want an LLM agent or a script to query Google Flights without writing your own request encoder, and you accept that the transport is a reverse-engineered endpoint with no contract. Do not adopt it if you need an official, supported data feed, or if you cannot tolerate a dependency that can break without a version bump.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 57 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem fli solves is the request format, not the flight data

Google Flights has no public API. Anyone who wants its results has two options: drive a browser and parse the rendered page, or reverse engineer the request the page itself sends. fli takes the second route. The README states that the library "directly interacts with Google Flights' API through reverse engineering" and contrasts this with libraries that "rely on web scraping." The claimed consequences are speed, no HTML parsing, no browser automation, and less exposure to UI changes.

That framing tells you who the project is for. It is not aimed at travel agencies or anyone who needs a contractually supported feed. It is aimed at engineers who want flight results inside a script or an agent and are willing to depend on an undocumented endpoint. The three surfaces (MCP server, CLI, Python package) all sit on the same search layer, so the target audience is really two groups: people building LLM tooling who need an MCP server, and people who want a command they can run from a terminal. The Python library is the substrate both of those use.

One search layer, three entry points, and a parameter set that maps onto Google's own query string

The architecture visible in the README is a single search implementation exposed three ways. The MCP server offers exactly two tools: search_flights for a specific date and search_dates for "the cheapest travel dates across a flexible date range." The CLI exposes the same capability as fli flights. The Python package is what both import.

The parameter tables are the most informative part of the documentation, because they show what the library actually encodes. search_flights takes origin and destination as IATA codes, comma-separated for multiple airports, plus departure_date in YYYY-MM-DD, an optional return_date, cabin_class (ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST), max_stops (ANY, NON_STOP, ONE_STOP, TWO_PLUS_STOPS), a departure_window in 'HH-HH' format such as '6-20', airline include and exclude lists, alliance include and exclude lists (ONEWORLD, SKYTEAM, STAR_ALLIANCE), min_layover and max_layover in minutes, and sort_by (CHEAPEST, DURATION, DEPARTURE_TIME, ARRIVAL_TIME).

Three of those parameters are documented as passing straight through to Google's query string: currency flows to curr=, language flows to hl=, and country flows to gl=. That is a small detail with a large implication. The library is not abstracting Google's query model away; it is exposing it. If you already understand how Google Flights encodes a search, the parameter list will read as familiar. If you do not, the README's own tables are the only specification you get, because the upstream endpoint has none.

search_dates mirrors most of that and swaps the single date for start_date, end_date, trip_duration and is_round_trip, with sort_by_price as a boolean instead of a sort_by enum. The asymmetry between the two tools is worth noting: one takes an enum, the other a flag. That is the kind of inconsistency that suggests the two code paths were written at different times.

Installing it: pipx for the CLI, pip for the library, and two MCP transports

The package name on PyPI is flights, not fli. The README gives pipx install flights for the CLI and MCP server, and pip install flights for library use. The executables it installs are fli, fli-mcp and fli-mcp-http.

fli-mcp runs the MCP server over STDIO. fli-mcp-http runs it over streamable HTTP and, per the README, serves at http://127.0.0.1:8000/mcp/. For Claude Desktop the documented configuration is a JSON block with an mcpServers entry named fli whose command is an absolute path to fli-mcp, for example /Users/<user>/.local/bin/fli-mcp. The README notes you can find that path with which fli-mcp. That detail matters more than it looks: GUI applications on macOS often do not inherit your shell PATH, so a bare fli-mcp in the config will frequently fail to launch, and the absolute path is the workaround.

CLI usage follows the shape fli flights JFK LHR 2026-10-25, with flags layered on top. The README's advanced example passes --time 6-20, --airlines BA,KL and --class BUSINESS. Note that the CLI flag names are not identical to the MCP parameter names: the MCP tool calls it departure_window and cabin_class, the CLI calls it --time and --class. If you move between the two surfaces, expect to translate.

On the protective features, the README lists rate limiting, automatic retries, comprehensive error handling and input validation. It does not state the retry count, the backoff schedule, or the rate limit threshold. Those numbers are not in the supplied material, so treat them as unknown until you read the source.

The reverse-engineered endpoint is the limitation, and the README does not address it

The README's own selling point is also its structural risk. "Reliable: Less prone to breaking from UI changes" is a comparative claim, not an absolute one. An undocumented endpoint can change its request or response shape at any time, and when it does, the failure will not arrive as a deprecation notice. It will arrive as a parse error or an empty result set. The library's input validation will not catch that, because the input was valid; the upstream contract simply moved.

The second limitation is scope. The README describes one-way and multi-city searches and a flexible date range search, and the parameter tables cover stops, layovers, alliances, cabin and passenger count. What is absent is any mention of booking, ticketing, price history, or seat availability beyond what a search returns. fli is a search client. If your requirement is to complete or manage a booking, this is the wrong tool, and the README makes no claim otherwise.

A third point is the language surface. The repository ships a JavaScript release line (fli-js-v0.0.4 and earlier), but the README documents only the Python package, the CLI and the MCP server. The README does not describe the JS package's API, so anyone arriving for the JavaScript side has no documented entry point in this material. That is a gap, not necessarily a defect.

Finally, the MCP server exposes two tools and nothing else. There is no tool for retrieving a specific itinerary by identifier, no caching layer described, and no mention of how results are shaped for the model. If you are building an agent, the tool surface is narrow by design, and you should expect to do the result handling yourself.

Where fli sits against a scraping-based client

The natural comparison is a browser-driven scraper such as a Playwright or Selenium wrapper around the Google Flights page. The difference in approach is concrete. A scraper renders the page, waits for the results grid, and reads values out of the DOM. fli skips rendering entirely and posts to the same endpoint the page posts to, then decodes the response.

That changes the failure modes rather than removing them. A scraper breaks when the markup changes: a class name, a container, a lazy-load trigger. fli breaks when the request or response schema changes. Scrapers also tend to be slower and heavier, since they carry a browser; fli's README claims speed as a direct consequence of dropping that layer. But scrapers have one advantage fli cannot match: when the page changes, you can look at the page and see what changed. When a reverse-engineered endpoint changes, you are reading minified JavaScript to find out why.

There is a second alternative worth naming: the official distribution channels. Airlines and aggregators publish supported APIs, and those come with terms, quotas and a schema that will not shift under you. They also come with onboarding, credentials and often cost. fli's value proposition is precisely that it skips all of that. The trade is legibility and support for immediacy. Neither choice is universally right, and the README's framing of the trade is one-sided.

Maintenance cost and the MIT licence

The repository is not archived and the last push recorded in the material is 2026-07-20, with the most recent releases being the fli-js-v0.0.4 line from 2026-05-24. Those JS releases are all 0.0.x, three of them published within about an hour of each other on the same day. That pattern reads as rapid iteration on a young package rather than a settled one. The Python package's version is not given in the supplied material.

For an adopter, the maintenance question is not whether the project is active but who absorbs breakage. Because the transport is reverse-engineered, a change upstream can require a code change in fli, and until that change ships you have no workaround short of pinning to an older release that also no longer works. Budget for the possibility that your integration breaks on a day when no fli release exists. If your use case is a personal script or an internal agent, that is tolerable. If it is a customer-facing booking flow, it is not.

The licence is MIT. That is permissive: it allows commercial use, modification and redistribution, and it comes with no warranty. The MIT grant covers the code in this repository. It does not cover Google Flights data, and it does not grant you any right to access Google's endpoint. Whether your use of that endpoint complies with Google's terms is a separate question that the licence text says nothing about. That is not legal advice; if the answer matters to your organisation, it is a question for counsel.

Editorial conclusion

Adopt fli if you want an LLM agent or a script to query Google Flights without writing your own request encoder, and you accept that the transport is a reverse-engineered endpoint with no contract. Do not adopt it if you need an official, supported data feed, or if you cannot tolerate a dependency that can break without a version bump. Before wiring it into anything, run fli-mcp-http and call search_flights with origin JFK, destination LHR and departure_date 2026-10-25, then check whether the response carries the currency, language and country you set.

Official sources

  1. License: MIT
  2. Project website
  3. punitarani/fli on GitHub
  4. README
  5. Releases
Community notes

Community notes