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;
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden. It mentions using Google Flights, implying external data, but doesn't disclose behavioral traits like rate limits, authentication needs, data freshness, or what happens with invalid inputs. For a 12-parameter tool with no annotations, this is a significant gap.

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, efficient sentence that directly states the tool's purpose without unnecessary words. It's front-loaded and appropriately sized for a search tool, with every word earning its place.

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 complexity (12 parameters, no annotations, no output schema), the description is incomplete. It doesn't explain what the tool returns (e.g., flight options, prices, details), error handling, or usage constraints. For a flight search tool with rich parameters, more context is needed to guide effective use.

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?

Schema description coverage is 100%, so the schema fully documents all parameters. The description adds no additional meaning beyond what's in the schema, such as explaining relationships between parameters or usage examples. Baseline 3 is appropriate when the schema does all the work.

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's purpose as searching for airport and flight information using Google Flights, which is specific (verb+resource). However, it doesn't distinguish this tool from potential flight search alternatives among the sibling tools, as none are flight-related, so differentiation isn't needed but the purpose is clear.

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. While there are no obvious flight-related siblings, it doesn't mention any context or prerequisites for usage, such as when flight searches are appropriate versus other travel tools like distance-matrix or get-directions.

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

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