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
18 changes: 17 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions google-flights/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dev.vars
node_modules/
dist/
88 changes: 88 additions & 0 deletions google-flights/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Google Flights MCP

An MCP server that provides flight search capabilities using Google Flights data and a comprehensive airports database.

## Features

- **Search Flights**: Search for one-way or round-trip flights between airports
- **Airport Search**: Find airport codes by name, city, or partial IATA code
- **Travel Date Calculator**: Get suggested departure and return dates
- **Airports Database**: Comprehensive database of 10,000+ airports with IATA codes

## Tools

### SEARCH_FLIGHTS

Search for flights between two airports.

**Parameters:**
- `fromAirport` (required): Departure airport IATA code (3 letters, e.g., 'LAX')
- `toAirport` (required): Arrival airport IATA code (3 letters, e.g., 'JFK')
- `departureDate` (required): Departure date in YYYY-MM-DD format
- `returnDate` (optional): Return date for round-trip flights
- `adults` (default: 1): Number of adult passengers
- `children` (default: 0): Number of children
- `infantsInSeat` (default: 0): Number of infants in seat
- `infantsOnLap` (default: 0): Number of infants on lap
- `seatClass` (default: 'economy'): Seat class (economy, premium_economy, business, first)

### SEARCH_AIRPORTS

Search for airport codes by name, city, or partial IATA code.

**Parameters:**
- `query` (required): Search term (minimum 2 characters)

**Examples:**
- "los angeles" → LAX - Los Angeles International Airport
- "london" → LHR, LGW, STN, LTN, etc.
- "JFK" → JFK - John F Kennedy International Airport

### GET_TRAVEL_DATES

Get suggested travel dates based on days from now and trip length.

**Parameters:**
- `daysFromNow` (default: 30): Days from today for departure
- `tripLength` (default: 7): Trip length in days

### UPDATE_AIRPORTS_DATABASE

Force refresh the airports database from the source CSV.

## Development

```bash
# Install dependencies (from mcps root)
bun install

# Start development server
bun run dev

# Type check
bun run check

# Build for production
bun run build
```

## Architecture

The MCP uses:
- **Airports CSV**: Fetched from [airportsdata](https://github.com/mborsetti/airportsdata) (10,000+ airports)
- **Bun runtime**: Fast JavaScript runtime for the server
- **Deco Runtime**: For MCP protocol handling

## Note on Flight Data

Google Flights does not provide a public API. This MCP generates Google Flights search URLs
that users can visit to see actual flight prices and options.

For production use with real-time flight pricing, consider integrating with:
- [Amadeus API](https://developers.amadeus.com/)
- [Skyscanner API](https://developers.skyscanner.net/)
- [Kiwi.com Tequila API](https://tequila.kiwi.com/)

## License

Private - See LICENSE file for details.
19 changes: 19 additions & 0 deletions google-flights/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"scopeName": "deco",
"name": "google-flights",
"friendlyName": "Google Flights",
"connection": {
"type": "HTTP",
"url": "https://sites-google-flights.decocache.com/mcp"
},
"description": "Search for flights using Google Flights data. Find flight options, compare prices, and get travel date suggestions.",
"icon": "https://www.gstatic.com/flights/app/lf1_64dp.png",
"unlisted": false,
"metadata": {
"categories": ["Travel"],
"official": false,
"tags": ["flights", "travel", "google-flights", "airports", "booking"],
"short_description": "Search flights and find airports using Google Flights data.",
"mesh_description": "The Google Flights MCP enables AI agents to search for flights between airports, find airport codes by city or name, and calculate travel dates. It provides access to a comprehensive database of 10,000+ airports with IATA codes, and generates Google Flights search URLs for users to view real-time pricing. Perfect for travel planning, flight comparison, and trip organization."
}
}
28 changes: 28 additions & 0 deletions google-flights/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@decocms/google-flights",
"version": "1.0.0",
"description": "Search for flights using Google Flights data",
"private": true,
"type": "module",
"scripts": {
"dev": "bun server/main.ts",
"build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js",
"build": "bun run build:server",
"publish": "cat app.json | deco registry publish -w /shared/deco -y",
"check": "tsc --noEmit"
},
"dependencies": {
"@decocms/bindings": "^1.0.7",
"@decocms/runtime": "^1.1.3",
"zod": "^4.0.0"
},
"devDependencies": {
"@decocms/mcps-shared": "1.0.0",
"@modelcontextprotocol/sdk": "1.25.1",
"deco-cli": "^0.28.0",
"typescript": "^5.7.2"
},
"engines": {
"node": ">=22.0.0"
}
}
29 changes: 29 additions & 0 deletions google-flights/server/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Constants for the Google Flights MCP
*/

/**
* CSV source for airport data from airportsdata project
*/
export const AIRPORTS_CSV_URL =
"https://raw.githubusercontent.com/mborsetti/airportsdata/refs/heads/main/airportsdata/airports.csv";

/**
* Cache TTL for airports data (24 hours)
*/
export const AIRPORTS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;

/**
* Default configuration for flight searches
*/
export const DEFAULT_CONFIG = {
maxResults: 10,
defaultTripDays: 7,
defaultAdvanceDays: 30,
seatClasses: ["economy", "premium_economy", "business", "first"] as const,
} as const;

/**
* Maximum number of airport search results to return
*/
export const MAX_AIRPORT_RESULTS = 20;
184 changes: 184 additions & 0 deletions google-flights/server/lib/airports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import type { AirportsMap } from "./types";

/**
* CSV source for airport data
*/
const AIRPORTS_CSV_URL =
"https://raw.githubusercontent.com/mborsetti/airportsdata/refs/heads/main/airportsdata/airports.csv";

/**
* In-memory cache for airports data
*/
let airportsCache: AirportsMap | null = null;
let lastFetchTime: number | null = null;
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

/**
* Parse CSV text into airport data
*/
function parseAirportsCsv(csvText: string): AirportsMap {
const airports: AirportsMap = new Map();
const lines = csvText.split("\n");

// Find header indices - parse header with CSV parser to handle quotes
const headerLine = lines[0] ?? "";
const header = parseCSVLine(headerLine).map((h) => h.trim().toLowerCase());
const iataIdx = header.indexOf("iata");
const nameIdx = header.indexOf("name");
const cityIdx = header.indexOf("city");
const countryIdx = header.indexOf("country");

if (iataIdx === -1 || nameIdx === -1) {
console.error(
"Could not find required columns in CSV. Headers found:",
header.slice(0, 6),
);
return airports;
}

// Parse data rows
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (!line?.trim()) continue;

// Simple CSV parsing (handles quoted fields)
const fields = parseCSVLine(line);
const iata = fields[iataIdx]?.trim() ?? "";
const name = fields[nameIdx]?.trim() ?? "";
const city = cityIdx >= 0 ? (fields[cityIdx]?.trim() ?? "") : "";
const country = countryIdx >= 0 ? (fields[countryIdx]?.trim() ?? "") : "";

// Only store entries with valid IATA codes (3 uppercase letters)
if (iata && iata.length === 3 && /^[A-Z]{3}$/.test(iata)) {
const fullName = city
? `${name}, ${city}, ${country}`
: `${name}, ${country}`;
airports.set(iata, fullName);
}
}

return airports;
}

/**
* Parse a single CSV line, handling quoted fields
*/
function parseCSVLine(line: string): string[] {
const fields: string[] = [];
let current = "";
let inQuotes = false;

for (let i = 0; i < line.length; i++) {
const char = line[i];

if (char === '"') {
if (inQuotes && line[i + 1] === '"') {
// Escaped quote
current += '"';
i++;
} else {
// Toggle quote state
inQuotes = !inQuotes;
}
} else if (char === "," && !inQuotes) {
fields.push(current);
current = "";
} else {
current += char;
}
}

fields.push(current);
return fields;
}

/**
* Fetch airports data from CSV source
*/
export async function fetchAirports(): Promise<AirportsMap> {
// Check cache
if (
airportsCache &&
lastFetchTime &&
Date.now() - lastFetchTime < CACHE_TTL_MS
) {
return airportsCache;
}

try {
const response = await fetch(AIRPORTS_CSV_URL);
if (!response.ok) {
throw new Error(`Failed to fetch airports: HTTP ${response.status}`);
}

const csvText = await response.text();
airportsCache = parseAirportsCsv(csvText);
lastFetchTime = Date.now();

console.log(`Loaded ${airportsCache.size} airports from CSV`);
return airportsCache;
} catch (error) {
console.error("Error fetching airports:", error);

// Return cached data if available, even if stale
if (airportsCache) {
return airportsCache;
}

// Return empty map if no cache available
return new Map();
}
}

/**
* Get airports from cache or fetch if not available
*/
export async function getAirports(): Promise<AirportsMap> {
if (airportsCache) {
return airportsCache;
}
return fetchAirports();
}

/**
* Search airports by query (code, name, or city)
*/
export async function searchAirports(
query: string,
): Promise<Array<{ code: string; name: string }>> {
const airports = await getAirports();
const normalizedQuery = query.toUpperCase().trim();
const results: Array<{ code: string; name: string }> = [];

for (const [code, name] of airports) {
if (
code.includes(normalizedQuery) ||
name.toUpperCase().includes(normalizedQuery)
) {
results.push({ code, name });
}
}

// Sort by code
results.sort((a, b) => a.code.localeCompare(b.code));

return results;
}

/**
* Check if an airport code exists
*/
export async function airportExists(code: string): Promise<boolean> {
const airports = await getAirports();
return airports.has(code.toUpperCase());
}

/**
* Get airport name by code
*/
export async function getAirportName(
code: string,
): Promise<string | undefined> {
const airports = await getAirports();
return airports.get(code.toUpperCase());
}
Loading
Loading