Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ uv run mkdocs build # Build static docs
3. **Search Engine** (`fli/search/`)
- `SearchFlights`: Core flight search using Google Flights API
- `SearchDates`: Find cheapest dates within date ranges
- `SearchExplore`: Discover cheap destinations from an origin (Google Flights Explore)
- Direct API integration (no web scraping)

4. **Data Models** (`fli/models/`)
Expand All @@ -73,7 +74,7 @@ uv run mkdocs build # Build static docs
- All models use Pydantic for validation

5. **MCP Server** (`fli/mcp/`)
- FastMCP-based server with four tools: `search_flights`, `search_dates`, `get_booking_options`, `find_airports`
- FastMCP-based server with five tools: `search_flights`, `search_dates`, `search_explore`, `get_booking_options`, `find_airports`
- Industry-standard parameter naming: `origin`, `destination`, `cabin_class`, `max_stops`
- Per-flight booking deep-link URLs (`tfs` protobuf) in every search result
- Prompt templates for guided searches
Expand All @@ -96,7 +97,7 @@ uv run mkdocs build # Build static docs
## Key Files and Entry Points

- `fli/cli/main.py` - CLI entry point and command registration
- `fli/mcp/server.py` - MCP server with `search_flights` and `search_dates` tools
- `fli/mcp/server.py` - MCP server with `search_flights`, `search_dates`, and `search_explore` tools
- `fli/core/parsers.py` - Shared parsing utilities
- `fli/core/builders.py` - Shared filter building utilities
- `fli/search/flights.py` - Core flight search implementation
Expand Down Expand Up @@ -145,6 +146,28 @@ Find cheapest travel dates within a range.
**Response:** Each date result carries a `booking_url` deep-linking to Google
Flights for that specific date (and return date for round trips).

### `search_explore`
Discover where you can fly cheaply when the destination is flexible
(Google Flights Explore / `GetExploreDestinations`). One call returns dozens
of destinations with their cheapest fares.

**Key Parameters:**
- `origin` - Airport IATA code (e.g. 'JFK') or a city knowledge-graph mid (e.g. '/m/04jpl')
- `departure_date` - Date in YYYY-MM-DD format (required — the endpoint errors without one)
- `destination` - ANYWHERE (default), EUROPE, SOUTHERN_EUROPE, ASIA, AFRICA,
NORTH_AMERICA, SOUTH_AMERICA, OCEANIA, a raw `/m/...` mid, or an IATA code
- `round_trip` / `trip_min_nights` / `trip_max_nights` - Round-trip pricing with a
trip-length window (nights, 0-23)
- `max_price`, `cabin_class`, `max_stops`, `airlines`, `exclude_airlines`,
`alliance`, `exclude_alliance`, `max_flight_duration` - Same semantics as `search_flights`
- `currency` / `language` / `country` - Same locale knobs as `search_flights`
- `sort_by_price` (default true), `limit`

**Response:** `destinations[]` with name, country, price (null when Google
found no fare), airline, stops, duration, `destination_airport`, dates,
coordinates, an image URL, and a `flights_url` deep link. Chain a result's
`destination_airport` into `search_flights` for bookable itineraries.

### `get_booking_options`
Get bookable fares (vendor names, prices, and direct booking URLs) for a
single itinerary. Runs a fresh search, selects the flight identified by
Expand Down
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ fli-mcp-http # serves at http://127.0.0.1:8000/mcp/

### MCP Tools Available

The MCP server provides two main tools:
The MCP server provides these main tools:

| Tool | Description |
|----------------------|-------------------------------------------------------------|
| **`search_flights`** | Search for flights on a specific date with detailed filters |
| **`search_dates`** | Find the cheapest travel dates across a flexible date range |
| **`search_explore`** | Discover cheap destinations from an origin ("fly anywhere") |

#### `search_flights` Parameters

Expand Down Expand Up @@ -101,6 +102,38 @@ The MCP server provides two main tools:
| `sort_by_price` | bool | Sort results by price (lowest first) |
| `passengers` | int | Number of adult passengers |

#### `search_explore` Parameters

Powered by Google Flights Explore (`GetExploreDestinations`): one call returns
dozens of destinations with their cheapest fares when the destination is
flexible.

| Parameter | Type | Description |
|-----------------------|--------|--------------------------------------------------------------|
| `origin` | string | Airport IATA code (e.g. 'JFK') or a city mid (e.g. '/m/04jpl') |
| `destination` | string | ANYWHERE (default), EUROPE, SOUTHERN_EUROPE, ASIA, AFRICA, NORTH_AMERICA, SOUTH_AMERICA, OCEANIA, a knowledge-graph mid, or an IATA code |
| `departure_date` | string | Departure date in YYYY-MM-DD format (required) |
| `round_trip` | bool | Price round trips instead of one-ways |
| `trip_min_nights` | int | Minimum trip length in nights (round trips, 0-23) |
| `trip_max_nights` | int | Maximum trip length in nights (round trips, 0-23) |
| `max_price` | int | Maximum fare cap |
| `cabin_class` | string | ECONOMY, PREMIUM_ECONOMY, BUSINESS, or FIRST |
| `max_stops` | string | ANY, NON_STOP, ONE_STOP, or TWO_PLUS_STOPS |
| `airlines` | list | Filter by airline codes (e.g., ['BA', 'AA']) |
| `exclude_airlines` | list | Airline IATA codes to **exclude** |
| `alliance` | list | Restrict to alliances: ONEWORLD, SKYTEAM, STAR_ALLIANCE |
| `exclude_alliance` | list | Alliance names to **exclude** |
| `max_flight_duration` | int | Maximum flight duration in minutes |
| `currency` | string | ISO 4217 currency code (e.g. 'EUR', 'JPY') |
| `language` | string | BCP-47 language code (e.g. 'en-GB') |
| `country` | string | ISO 3166-1 alpha-2 country code (e.g. 'GB') |
| `sort_by_price` | bool | Sort destinations by price (default true) |
| `limit` | int | Maximum number of destinations to return |

Each priced destination includes the airline, stops, duration, destination
airport, and a `flights_url` deep link; pass the `destination_airport` to
`search_flights` for bookable itineraries.

## Quick Start

```bash
Expand Down
73 changes: 73 additions & 0 deletions docs/guides/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,79 @@ Find the cheapest travel dates between two airports within a date range.
Each date result carries a `booking_url` deep-linking to Google Flights for
that specific date (and return date for round trips).

### `search_explore`

Discover where you can fly cheaply when the destination is flexible — powered
by Google Flights Explore (`GetExploreDestinations`). One call returns dozens
of destinations with their cheapest fares for an origin and a broad
destination like `ANYWHERE` or a continent.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `origin` | string | Yes | - | Airport IATA code (e.g., 'JFK') or a city knowledge-graph mid (e.g., '/m/04jpl' for London) |
| `departure_date` | string | Yes | - | Departure date in YYYY-MM-DD format |
| `destination` | string | No | ANYWHERE | ANYWHERE, EUROPE, SOUTHERN_EUROPE, ASIA, AFRICA, NORTH_AMERICA, SOUTH_AMERICA, OCEANIA, a knowledge-graph mid (e.g., '/m/02j9z'), or an IATA code |
| `round_trip` | bool | No | false | Price round trips instead of one-ways |
| `trip_min_nights` | int | No | null | Minimum trip length in nights (round trips, 0-23) |
| `trip_max_nights` | int | No | null | Maximum trip length in nights (round trips, 0-23) |
| `max_price` | int | No | null | Maximum fare cap |
| `cabin_class` | string | No | ECONOMY | ECONOMY, PREMIUM_ECONOMY, BUSINESS, or FIRST |
| `max_stops` | string | No | ANY | ANY, NON_STOP, ONE_STOP, or TWO_PLUS_STOPS |
| `airlines` | list | No | null | Filter by airline codes (e.g., ['BA', 'AA']) |
| `exclude_airlines` | list | No | null | Airline IATA codes to **exclude** |
| `alliance` | list | No | null | Restrict to ONEWORLD / SKYTEAM / STAR_ALLIANCE |
| `exclude_alliance` | list | No | null | Alliance(s) to **exclude** |
| `max_flight_duration` | int | No | null | Maximum flight duration in minutes |
| `currency` | string | No | null | ISO 4217 currency code (`curr=`) |
| `language` | string | No | null | BCP-47 language code (`hl=`) |
| `country` | string | No | null | ISO 3166-1 alpha-2 country (`gl=`) |
| `sort_by_price` | bool | No | true | Sort destinations by price (lowest first) |
| `limit` | int | No | null | Maximum number of destinations to return |

**Example Response:**

```json
{
"success": true,
"origin": "LHR",
"origin_name": "London",
"destination": "EUROPE",
"region_name": "Europe",
"departure_date": "2026-09-10",
"trip_type": "ONE_WAY",
"count": 77,
"priced_count": 55,
"destinations": [
{
"name": "Edinburgh",
"country": "United Kingdom",
"mid": "/m/02m77",
"price": 21.0,
"currency": "GBP",
"departure_date": "2026-09-10",
"arrival_date": "2026-09-10",
"airline": "RK",
"airline_name": "Ryanair UK",
"stops": 0,
"duration_minutes": 80,
"destination_airport": "EDI",
"latitude": 55.953252,
"longitude": -3.188267,
"image_url": "https://encrypted-tbn3.gstatic.com/images?q=...",
"flights_url": "https://www.google.com/travel/flights?q=Flights%20from%20LHR%20to%20EDI%20on%202026-09-10"
}
]
}
```

Some destinations come back without a price (`price: null`) — Google found no
itinerary matching the filters for them. Follow up with
[`search_flights`](#search_flights) using a result's `destination_airport` and
`departure_date` for bookable itineraries; each priced destination also
carries a ready-made `flights_url` deep link.

### `get_booking_options`

Get bookable fares — vendor names, prices, and **direct booking URLs** — for a
Expand Down
46 changes: 46 additions & 0 deletions examples/python/explore_anywhere.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Explore search: find the cheapest destinations from an origin.

Uses Google Flights Explore to answer "where can I fly cheaply?" — one
request returns dozens of destinations with their cheapest fares.
"""

from datetime import datetime, timedelta

from fli.models import Airport, ExploreRegion, ExploreSearchFilters
from fli.search import SearchExplore


def main() -> None:
"""Search for the cheapest places to fly from London to anywhere in Europe."""
departure_date = (datetime.now() + timedelta(days=45)).strftime("%Y-%m-%d")

filters = ExploreSearchFilters(
origin=Airport.LHR,
destination=ExploreRegion.EUROPE, # or ANYWHERE, ASIA, a raw ExplorePlace mid...
departure_date=departure_date,
)

result = SearchExplore().search(filters, currency="GBP")
if result is None:
print("Search failed")
return

priced = sorted(
(d for d in result.destinations if d.price is not None),
key=lambda d: d.price,
)
print(
f"{len(result.destinations)} destinations from {result.origin_name} "
f"on {departure_date} ({len(priced)} priced)\n"
)
for destination in priced[:10]:
print(
f"{destination.name:15} {destination.country or '':15} "
f"£{destination.price:>6.0f} {destination.airline_name or destination.airline}"
f" -> {destination.destination_airport}"
f" ({destination.stops} stops, {destination.duration_minutes} min)"
)


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions fli/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@
try:
from fli.mcp.server import (
DateSearchParams,
ExploreSearchParams,
FlightSearchParams,
mcp,
run,
run_http,
search_dates,
search_explore,
search_flights,
)

__all__ = [
"DateSearchParams",
"ExploreSearchParams",
"FlightSearchParams",
"search_dates",
"search_explore",
"search_flights",
"mcp",
"run",
Expand Down
Loading