# fli: A Google Flights MCP Server, CLI and Python Library

> fli talks to Google Flights through a reverse engineered API rather than HTML scraping, and exposes the same search logic as a Python library, a Typer CLI and a FastMCP server. It is a beta tool for developers who need flight search inside an agent or a script, not a booking system.

**punitarani/fli** — Google Flights MCP, CLI and Python Library

- Repository: https://github.com/punitarani/fli
- Website: https://deepwiki.com/punitarani/fli
- Stars: 3,201 · Forks: 398
- Language: Python
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/punitarani-fli

## The problem fli solves: flight search as a callable tool, not a web page

Most flight data projects in Python end up parsing HTML. They drive a headless browser, wait for results to render, and then pick apart markup that changes without notice. fli takes the other route. The README states that the library "directly interacts with Google Flights' API through reverse engineering", so there is no HTML parsing and no browser automation in the data path. The project describes the consequences it expects from that choice: faster and more reliable results, and less exposure to UI changes.

The audience is narrow and specific. This is for developers who already know which airports they want and want the result set as structured data inside another program. The README lists one-way and multi-city searches, flexible departure windows, multi-airline support, cabin class selection, stop preferences and custom sorting. Those are the primitives an agent needs when a user says "find me something under 400 EUR to Lisbon in the second week of October". The package name on PyPI is flights, the importable module is fli, and the console scripts are fli, fli-mcp and fli-mcp-http. That naming split is worth internalising early, because the install command and the command you type afterwards do not match.

## How fli reaches Google Flights: parameters in, structured results out

The mechanism visible in the README is a parameter translation layer. Callers supply origin and destination IATA codes (comma-separated for multiple airports), a departure date in YYYY-MM-DD, an optional return date, a cabin class, a stop limit, a departure window in HH-HH format, airline include and exclude lists, alliance filters, layover bounds in minutes, a passenger count and a sort order. The library maps those onto the query Google Flights itself uses.

The README is explicit about three of those mappings: currency flows to the curr= parameter, language flows to hl=, and country flows to gl=. That matters more than it looks. Price and availability in this data set are a function of the point of sale, so a search issued with currency EUR, language en-GB and country GB is not the same query as one issued with USD and en-US. The README documents the keys but does not document how the point-of-sale combination changes results, so treat those three fields as part of your test matrix rather than defaults you set once.

The two MCP tools split the work. search_flights handles one specific date. search_dates walks a range between start_date and end_date and can take a trip_duration and an is_round_trip flag, which is the shape you want for "when is this route cheapest". Both expose the same filter vocabulary, with one difference: search_dates uses sort_by_price as a boolean rather than the sort_by enum used by search_flights.

Underneath, the dependency list tells you what the implementation leans on: curl-cffi and httpx for transport, pydantic for models and input validation, tenacity for retries, ratelimit for throttling, babel for locale handling and plotext for terminal charts. The README groups rate limiting, automatic retries, error handling and input validation under "Built-in Protection", which is the right place to look when a search fails: the failure is more likely to be a rejected parameter or a throttled request than a parsing bug.

## Installing fli and running your first search

The README recommends pipx for the CLI, because the console scripts need to be on your PATH. The distribution name is flights, not fli.

```bash
pipx install flights
fli --help
```

If you only need the library inside an existing environment, plain pip works:

```bash
pip install flights
```

A basic one-way search takes origin, destination and date as positional arguments. The README gives this example, with a 6 AM to 8 PM departure window, British Airways and KLM only, and business class:

```bash
fli flights JFK LHR 2026-10-25 \
    --time 6-20 \
    --airlines BA,KL \
    --class BUSINESS
```

You should see a table of itineraries in the terminal. Note that the README's own snippet annotates those flags with inline comments, which will not survive a copy-paste into a shell; strip them.

To use it from an MCP client, run the server and point the client at the binary. The README's Claude Desktop configuration looks like this:

```json
{
  "mcpServers": {
    "fli": {
      "command": "/Users/<user>/.local/bin/fli-mcp"
    }
  }
}
```

Replace <user> with your username, or run which fli-mcp to get the real path. The server runs over STDIO by default. If you want HTTP instead, fli-mcp-http serves the streamable transport at http://127.0.0.1:8000/mcp/, and the repository also ships a docker-compose.yml that maps port 8000 and sets HOST to 0.0.0.0 and PORT to 8000, with a healthcheck against /health. The Dockerfile builds with uv sync --frozen --no-dev --extra mcp and runs as a non-root appuser.

## Where fli breaks: undocumented endpoints, missing booking, and a beta label

The central design decision is also the central risk. A reverse engineered API is undocumented by definition, and it belongs to someone else. Google can change request shapes, add signing, or rate limit by IP without notice, and when that happens there is no changelog to read. The README claims the approach is less prone to breaking from UI changes, which is fair, but it does not claim immunity to API changes. If you build a product on this, you are depending on an interface that was never offered to you.

There is no booking. Nothing in the README describes holding a fare, ticketing, or writing back to an itinerary. fli reads search results. If your requirement is to complete a purchase, this is the wrong tool and no amount of filtering will change that.

The package also labels itself honestly: pyproject.toml carries the classifier Development Status :: 4 - Beta, and the version is 0.10.0. The most recent release entries in the repository are fli-js-v0.0.4, fli-js-v0.0.3 and fli-js-v0.0.2, all dated 2026-05-24, which suggests the JavaScript side is much younger than the Python one. The last push to the default branch was on 2026-07-20.

Finally, the README documents the parameter surface but not the operational envelope. It does not state a request budget, a timeout, or what tenacity and ratelimit are configured to do. It does not document rollback or a migration path between versions. Those are reasonable things to want from a library that touches a live external service, and their absence is the honest state of the documentation rather than a hidden feature.

## fli versus scraping-based flight libraries

The obvious alternative is a scraping library or a general purpose web automation stack, and the difference is not cosmetic. A scraper renders the Google Flights page and reads the DOM. That means a browser or at least an HTTP client that can satisfy the page's JavaScript, a parser tied to the current markup, and a maintenance cycle that restarts every time Google reshuffles a class name or a layout. In exchange you get the page exactly as a human sees it, including whatever the page shows that the underlying API call does not return.

fli inverts the trade. You send a structured request and receive structured results, and the parsing problem largely disappears. The cost is that you are now coupled to a private contract. A scraper breaks visibly, because the page stops parsing. A reverse engineered client breaks quietly, because the endpoint still returns 200 with a differently shaped body. That is the failure mode to design around: validate the shape of what comes back, not just the status code.

A second alternative is a commercial flight data API with a contract and a support address. It will cost money and it will not return Google Flights prices, because it is a different inventory view. If your requirement is a defensible, documented data source, that is the category to compare against, and fli is not competing there. fli competes with the scraper on speed and maintenance, and with the commercial API on cost.

## Maintenance cost, packaging and the MIT licence

The upgrade surface is small but real. The Python package requires Python 3.10 or newer and declares support through 3.13. Runtime dependencies are babel, curl-cffi, httpx, plotext, pydantic, python-dotenv, ratelimit, tenacity and typer. The MCP server is an optional extra: fastapi, fastmcp, pydantic-settings and uvicorn, installed via the mcp extra or the all extra. If you do not run the server, you do not carry FastAPI and uvicorn.

The repository ships a Makefile that assumes uv: make install runs uv sync, make mcp runs uv run fli-mcp, and make test runs pytest. There is a tox.ini and a pytest.ini, and the Dockerfile pins the uv image at 0.6.0. The practical maintenance question is not how often you upgrade fli, but how quickly you can detect that Google changed something upstream. A pinned version plus a smoke test against a known route will tell you more than a changelog will.

Licensing is straightforward: the project is MIT, declared both in LICENSE.txt and in the pyproject.toml license field. MIT permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That covers the code. It does not cover the data. Flight results originate from Google Flights, and the MIT grant says nothing about your right to store, republish or resell those results. That is a separate question and one this article cannot answer; read Google's terms for your own use case rather than assuming the permissive licence extends to the output.

## Conclusion

Adopt fli if you want Google Flights results inside Claude Desktop, another MCP client, or a Python script, and you are comfortable with a beta package whose data path is an undocumented endpoint. Do not adopt it if you need booking, ticketing, price guarantees or a supported commercial SLA. Before committing, check the search_flights parameter list against the routes you care about, confirm the currency, language and country codes you pass produce the prices you expect, and read the MIT licence text for your own redistribution case.

## FAQ

### Is there a free API that can search for flights?

fli is a free, MIT licensed Python package that searches Google Flights through a reverse engineered API, and it is available on PyPI as flights. Free here describes the software, not the underlying data: the README does not state any terms for the Google Flights results themselves. You install it with pip install flights or pipx install flights.

### Is the Google Flight API free?

There is no public Google Flights API documented in this repository. fli reaches Google Flights by reverse engineering the request the site itself makes, which the README describes as "direct API access" with "zero scraping". Because that endpoint is undocumented, no pricing, quota or terms are stated anywhere in the documentation.

### How do I run the fli MCP server over HTTP instead of STDIO?

Use the fli-mcp-http console script, which the README says serves at http://127.0.0.1:8000/mcp/. The repository's docker-compose.yml runs the same server on port 8000 with HOST set to 0.0.0.0 and PORT set to 8000, and includes a healthcheck against the /health path.

### What is the difference between search_flights and search_dates in fli?

search_flights searches one specific departure_date and accepts a sort_by enum of CHEAPEST, DURATION, DEPARTURE_TIME or ARRIVAL_TIME. search_dates scans a range between start_date and end_date, optionally with a trip_duration and is_round_trip, and sorts by price using the boolean sort_by_price instead.

### Can fli book a flight or hold a fare?

No. The README describes search and filtering only: searching flights on a date and finding the cheapest travel dates across a range. Booking, ticketing and fare holds are not mentioned anywhere in the documented tool set.

## Sources

- [License: MIT](https://github.com/punitarani/fli/blob/main/LICENSE)
- [Project website](https://deepwiki.com/punitarani/fli)
- [punitarani/fli on GitHub](https://github.com/punitarani/fli)
- [README](https://github.com/punitarani/fli/blob/main/README.md)
- [Releases](https://github.com/punitarani/fli/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/punitarani-fli
