Skip to main content
Glama

airports-search

Search for flight options and airport information between specified departure and arrival airports using Google Flights data. Find available flights, compare prices, and get essential travel details for trip planning.

Instructions

Search for airport and flight information using Google Flights

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
departure_idYesDeparture airport code (e.g., 'CDG', 'AUS', 'LAX', 'SFO'). Use 3-letter IATA codes.
arrival_idYesArrival airport code (e.g., 'NRT', 'LAX', 'JFK', 'LHR'). Use 3-letter IATA codes.
outbound_dateNoOutbound date in YYYY-MM-DD format
return_dateNoReturn date in YYYY-MM-DD format for round-trip (REQUIRED when type=1)
multi_city_jsonNoJSON string for multi-city trips with multiple legs (required when type=3)
typeNoTrip type: 1=Round-trip (requires return_date), 2=One-way (default), 3=Multi-city (requires multi_city_json). Use type=2 for one-way trips to avoid errors.
max_best_flightsNoMaximum number of best flights to return
max_other_flightsNoMaximum number of other flights to return
include_price_insightsNoInclude price insights and history
include_airportsNoInclude airport information
summary_onlyNoReturn only essential flight information
include_linksNoInclude booking links for each flight (off by default)

Implementation Reference

  • The core handler function searchAirports that queries SerpAPI for Google Flights data using the provided parameters and returns formatted Markdown output.
    export async function searchAirports(
      params: z.infer<typeof airportsSearchSchema>
    ): Promise<string> {
      const apiKey = process.env.SERP_API_KEY;
      if (!apiKey) {
        throw new Error("SERP_API_KEY environment variable is required");
      }
    
      const {
        max_best_flights,
        max_other_flights,
        include_price_insights,
        include_airports,
        summary_only,
        include_links,
        ...apiParams
      } = params;
    
      const searchParams = new URLSearchParams({
        engine: "google_flights",
        api_key: apiKey,
        departure_id: apiParams.departure_id,
        arrival_id: apiParams.arrival_id,
        hl: "en",
        currency: "USD",
        ...(apiParams.outbound_date && { outbound_date: apiParams.outbound_date }),
        ...(apiParams.return_date && { return_date: apiParams.return_date }),
        ...(apiParams.multi_city_json && {
          multi_city_json: apiParams.multi_city_json,
        }),
        ...(apiParams.type !== undefined && { type: apiParams.type.toString() }),
      });
    
      try {
        const response = await axios.get(
          `${SERPAPI_BASE_URL}?${searchParams.toString()}`
        );
        return formatFlightToMarkdown(response.data, params);
      } catch (error) {
        if (axios.isAxiosError(error)) {
          throw new Error(
            `Airports API request failed: ${
              error.response?.data?.error || error.message
            }`
          );
        }
        throw error;
      }
    }
  • Zod schema defining input parameters for the airports-search tool, including airports, dates, trip type, and formatting options.
    const airportsSearchSchema = z.object({
      departure_id: z
        .string()
        .describe("Departure airport code (e.g., 'CDG', 'AUS', 'LAX', 'SFO'). Use 3-letter IATA codes."),
      arrival_id: z.string().describe("Arrival airport code (e.g., 'NRT', 'LAX', 'JFK', 'LHR'). Use 3-letter IATA codes."),
      outbound_date: z
        .string()
        .optional()
        .describe("Outbound date in YYYY-MM-DD format"),
      return_date: z
        .string()
        .optional()
        .describe("Return date in YYYY-MM-DD format for round-trip (REQUIRED when type=1)"),
      multi_city_json: z
        .string()
        .optional()
        .describe("JSON string for multi-city trips with multiple legs (required when type=3)"),
      type: z
        .number()
        .optional()
        .default(2)
        .describe("Trip type: 1=Round-trip (requires return_date), 2=One-way (default), 3=Multi-city (requires multi_city_json). Use type=2 for one-way trips to avoid errors."),
      max_best_flights: z
        .number()
        .optional()
        .default(3)
        .describe("Maximum number of best flights to return"),
      max_other_flights: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of other flights to return"),
      include_price_insights: z
        .boolean()
        .optional()
        .default(true)
        .describe("Include price insights and history"),
      include_airports: z
        .boolean()
        .optional()
        .default(false)
        .describe("Include airport information"),
      summary_only: z
        .boolean()
        .optional()
        .default(true)
        .describe("Return only essential flight information"),
      include_links: z
        .boolean()
        .optional()
        .default(false)
        .describe("Include booking links for each flight (off by default)"),
    });
  • src/index.ts:151-179 (registration)
    MCP server tool registration for 'airports-search', using the schema and wrapping searchAirports handler with error handling and content formatting.
    server.tool(
      "airports-search",
      "Search for airport and flight information using Google Flights",
      airportsSearchSchema.shape,
      async (params) => {
        try {
          const result = await searchAirports(params);
          return {
            content: [
              {
                type: "text",
                text: result,
              },
            ],
          };
        } catch (error) {
          return {
            content: [
              {
                type: "text",
                text: `Error searching airports data: ${
                  error instanceof Error ? error.message : String(error)
                }`,
              },
            ],
          };
        }
      }
    );
  • Helper function that formats the raw SerpAPI flight data into a structured Markdown report, handling best/other flights, durations, layovers, and price insights.
    function formatFlightToMarkdown(data: any, params: z.infer<typeof airportsSearchSchema>): string {
      if (!data) return "No flight data available.";
    
      let markdown = `# ${params.departure_id} → ${params.arrival_id}\n\n`;
      
      // Get the Google Flights URL for booking
      const googleFlightsUrl = data.search_metadata?.google_flights_url;
    
      // Add route summary
      if (params.summary_only) {
        const bestFlight = data.best_flights?.[0];
        const bestPrice = bestFlight?.price || data.price_insights?.lowest_price;
        const flightCount = (data.best_flights?.length || 0) + (data.other_flights?.length || 0);
        
        markdown += `**Best Price**: $${bestPrice}\n`;
        if (bestFlight?.flights?.[0]?.airline) {
          markdown += `**Airline**: ${bestFlight.flights[0].airline}\n`;
        }
        markdown += `**Flights Found**: ${flightCount}\n\n`;
    
        if (params.include_price_insights !== false && data.price_insights) {
          markdown += `**Price Level**: ${data.price_insights.price_level}\n`;
          if (data.price_insights.typical_price_range) {
            markdown += `**Typical Range**: $${data.price_insights.typical_price_range.join('-$')}\n`;
          }
        }
        return markdown;
      }
    
      // Add best flights
      if (data.best_flights && params.max_best_flights && params.max_best_flights > 0) {
        markdown += `## Best Options\n\n`;
        const bestFlights = data.best_flights.slice(0, params.max_best_flights);
        
        bestFlights.forEach((flight: any) => {
          const stops = flight.flights && flight.flights.length > 1 ? `${flight.flights.length - 1} stop${flight.flights.length > 2 ? 's' : ''}` : 'Direct';
          const totalDuration = formatDuration(flight.total_duration);
          markdown += `### $${flight.price} • ${totalDuration} • ${stops}\n`;
          if (params.include_links && googleFlightsUrl) {
            markdown += `**URL**: ${googleFlightsUrl}\n`;
          }
          
          if (flight.flights && flight.flights.length > 0) {
            flight.flights.forEach((segment: any, index: number) => {
              const depAirport = segment.departure_airport?.code || segment.departure_airport?.name || segment.departure_airport || 'Unknown';
              const arrAirport = segment.arrival_airport?.code || segment.arrival_airport?.name || segment.arrival_airport || 'Unknown';
              const depTime = segment.departure_airport?.time || 'Unknown';
              const arrTime = segment.arrival_airport?.time || 'Unknown';
              markdown += `${segment.airline} ${segment.flight_number}: ${depAirport} ${depTime} → ${arrAirport} ${arrTime}\n`;
              
              // Add layover information if this is not the last segment
              if (index < flight.flights.length - 1 && flight.layovers?.[index]) {
                const layover = flight.layovers[index];
                const layoverDuration = formatDuration(layover.duration);
                markdown += `  Layover: ${layoverDuration}\n`;
              }
            });
          }
          markdown += `\n`;
        });
      }
    
      // Add other flights
      if (data.other_flights && params.max_other_flights && params.max_other_flights > 0) {
        markdown += `## Other Options\n\n`;
        const otherFlights = data.other_flights.slice(0, params.max_other_flights);
        
        otherFlights.forEach((flight: any) => {
          const stops = flight.flights && flight.flights.length > 1 ? `${flight.flights.length - 1} stop${flight.flights.length > 2 ? 's' : ''}` : 'Direct';
          const totalDuration = formatDuration(flight.total_duration);
          markdown += `### $${flight.price} • ${totalDuration} • ${stops}\n`;
          if (params.include_links && googleFlightsUrl) {
            markdown += `**URL**: ${googleFlightsUrl}\n`;
          }
          
          if (flight.flights && flight.flights.length > 0) {
            flight.flights.forEach((segment: any, index: number) => {
              const depAirport = segment.departure_airport?.code || segment.departure_airport?.name || segment.departure_airport || 'Unknown';
              const arrAirport = segment.arrival_airport?.code || segment.arrival_airport?.name || segment.arrival_airport || 'Unknown';
              const depTime = segment.departure_airport?.time || 'Unknown';
              const arrTime = segment.arrival_airport?.time || 'Unknown';
              markdown += `${segment.airline} ${segment.flight_number}: ${depAirport} ${depTime} → ${arrAirport} ${arrTime}\n`;
              
              // Add layover information if this is not the last segment
              if (index < flight.flights.length - 1 && flight.layovers?.[index]) {
                const layover = flight.layovers[index];
                const layoverDuration = formatDuration(layover.duration);
                markdown += `  Layover: ${layoverDuration}\n`;
              }
            });
          }
          markdown += `\n`;
        });
      }
    
      // Add price insights for detailed view
      if (params.include_price_insights !== false && data.price_insights && !params.summary_only) {
        markdown += `**Price Level**: ${data.price_insights.price_level} • Lowest: $${data.price_insights.lowest_price}\n`;
      }
    
      return markdown;
    }
  • Utility helper to format flight durations from minutes into HH:MM string format.
    function formatDuration(minutes: number | string): string {
      const mins = typeof minutes === 'string' ? parseInt(minutes) : minutes;
      if (isNaN(mins)) return 'Unknown';
      const hours = Math.floor(mins / 60);
      const remainingMins = mins % 60;
      return `${hours}:${remainingMins.toString().padStart(2, '0')}`;
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure but only states the basic function. It doesn't mention rate limits, authentication needs, data freshness, error conditions, or what kind of results to expect (e.g., flight prices, schedules). For a complex search tool with 12 parameters, this is inadequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, clear sentence that efficiently communicates the core purpose without unnecessary words. It's appropriately sized and front-loaded, making it easy for an agent to quickly understand the tool's function.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (12 parameters, no output schema, no annotations), the description is insufficient. It doesn't explain what the output looks like (flight data format), behavioral constraints, or error handling. For a flight search tool that likely returns structured data, more context is needed to help the agent use it effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so all parameters are well-documented in the schema itself. The description adds no additional parameter semantics beyond what's already in the schema, maintaining the baseline score of 3 since the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches for airport and flight information using Google Flights, specifying both the resource (airport/flight info) and the data source (Google Flights). However, it doesn't differentiate from sibling tools like 'places-search' or 'distance-matrix' that might also handle location-based queries, so it misses full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention sibling tools or other contexts where flight search might be preferred over general place searches or distance calculations, leaving the agent with no explicit usage instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaptainCrouton89/maps-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server