Skip to main content
Glama
ricleedo

Google Services MCP Server

by ricleedo

airports-search

Search for flight information between airports using Google Flights. Find available flights, prices, and schedules for one-way, round-trip, or multi-city trips.

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

  • Main handler function that queries SerpAPI for Google Flights data using provided airport codes and dates, then formats the response into Markdown using helper function.
    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 airport codes, dates, trip types, 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)
    Registers the airports-search tool on the MCP server with description, schema reference, and async handler that calls searchAirports and wraps response in MCP format with error handling.
    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)
                }`,
              },
            ],
          };
        }
      }
    );
  • Key helper function that formats raw SerpAPI Google Flights JSON response into structured Markdown output, handling best/other flights, summaries, price insights, layovers, and links.
    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;
    }

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/ricleedo/Google-Service-MCP'

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