Skip to main content
Glama

search_flights

Search for flights using the Aviasales API to find available options based on travel segments, passenger count, class, and currency. Retrieve search results for further filtering and booking.

Instructions

Search for flights using the Aviasales Flight Search API. This tool performs search based on the provided flight segments, number of passengers, trip class, currency, and locale. It provides search_id and description of search results and saves found options internally.After receiving the result client can use get_flight_options tool to retrieve the found options with more granular filters.IMPORTANT: All times are local to departure/arrival locations and use HH:MM 24-hour format.IMPORTANT: Call this tool as many times as needed to find the best flight options.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
requestYes

Implementation Reference

  • The main asynchronous handler function implementing the search_flights tool logic. It prepares the request body, generates the API signature, initiates the search via POST, polls the results endpoint every 5 seconds for up to 90 seconds, aggregates and parses flight proposals, caches them, reports progress, and returns a summary description of the results.
    async def search_flights(
        request: SearchRequestModel,
        ctx: Context
    ) -> Dict[str, Any]:
        """Search for flights using Travelpayouts Flight Search API."""
        
        request_body = request.model_dump()
        request_body["token"] = API_TOKEN
        request_body["marker"] = MARKER
        request_body["passengers"] = {
            "adults": request.adults,
            "children": request.children,
            "infants": request.infants
        }
        del request_body["adults"]
        del request_body["children"]
        del request_body["infants"]
    
        signature = _generate_signature(API_TOKEN, request_body)
        request_body["signature"] = signature
    
        async with httpx.AsyncClient(timeout=40) as client:
            init_resp = await client.post(SEARCH_URL, json=request_body)
            if init_resp.status_code != 200:
                raise ToolError(f"Aviasales API returned non-200 status code: {init_resp.status_code}, raw text: {init_resp.text}")
            
            init_data = init_resp.json()
            search_id = init_data["search_id"]
            set_currency_rates(init_data["currency_rates"])
    
            deadline = datetime.now() + timedelta(seconds=90)  
            batch_proposals = None
    
            while datetime.now() < deadline:
                await asyncio.sleep(5)
                res_r = await client.get(f"{RESULTS_URL}?uuid={search_id}")
                res_r.raise_for_status()
                res_json = res_r.json()
    
                # Defensive: ensure we got a *list* per the API spec.
                if not isinstance(res_json, list):
                    raise ToolError("Unexpected response format: expected a list of results")
    
                # Aggregate proposals from every object that contains them.
                for obj in res_json:
                    if isinstance(obj, dict) and obj.get("proposals"):
                        try:
                            if not batch_proposals:
                                batch_proposals = parse_proposals_batch(obj)
                            else:
                                batch_proposals = batch_proposals.combine_with(parse_proposals_batch(obj))
                        except Exception as e:
                            print(f"Error parsing proposals: \n {json.dumps(obj, indent=2)}", file=sys.stderr)
                            raise
    
                        ctx.report_progress(progress=len(batch_proposals.proposals), total=None, message=f"Found {len(batch_proposals.proposals)} options so far...")
                    if set(obj.keys()) == {"search_id"}:
                        search_results_cache[search_id] = batch_proposals
                        return batch_proposals.get_description()
    
            search_results_cache[search_id] = batch_proposals
            return batch_proposals.get_description() if batch_proposals else "No proposals found until the search timed out."
  • Pydantic models defining the input schema for the search_flights tool, including segment details and overall search parameters like passengers, class, currency, and locale.
    class SearchRequestSegmentModel(BaseModel):
        origin: str = Field(..., description='Origin IATA (this can be airport IATA or in case city has multiple airports better to use city IATA). The IATA code is shown in uppercase letters LON or MOW')
        destination: str = Field(..., description='Destination IATA (this can be airport IATA or in case city has multiple airports better to use city IATA). The IATA code is shown in uppercase letters LON or MOW')
        date: str = Field(..., description="Departure date in YYYY-MM-DD format")
    
    class SearchRequestModel(BaseModel):
        """Search request model for Travelpayouts Flight Search API."""
        segments: List[SearchRequestSegmentModel] = Field(..., description='''List of CONNECTED flight segments for the same journey. Each segment represents one leg of a multi-city trip or round trip.
            IMPORTANT: Do NOT use multiple segments for alternative dates of the same route. For flexible dates, perform separate searches.
            
            Examples:
            - One way: [{'origin': 'SFO', 'destination': 'LAX', 'date': '2023-10-01'}]
            - Round trip: [{'origin': 'SFO', 'destination': 'LAX', 'date': '2023-10-01'}, {'origin': 'LAX', 'destination': 'SFO', 'date': '2023-10-15'}]
            - Multi-city: [{'origin': 'SFO', 'destination': 'LAX', 'date': '2023-10-01'}, {'origin': 'LAX', 'destination': 'JFK', 'date': '2023-10-05'}]
            
            For alternative dates (e.g., 'July 13 OR July 14'), use separate calls of this tool.''')
        adults: int = Field(1, ge=1, le=9, description="Number of adult passengers (12 years old and older)")
        children: int = Field(0, ge=0, le=6, description="Number of children (2-11 years old)")
        infants: int = Field(0, ge=0, le=6, description="Number of infants (under 2 years old)")
        trip_class: str = Field("Y", description="Trip class - single letter: Y for economy, C for business. Default is Y (economy class)")
        currency: str = Field("USD", description="Currency code (default is USD)")
        locale: str = Field("en", description="Locale for the response (default is en). These are the supported locales: en-us, en-gb, ru, de, es, fr, pl")
  • The @mcp.tool decorator that registers the search_flights function as an MCP tool, including its detailed description.
    @mcp.tool(
        description="Search for flights using the Aviasales Flight Search API. " \
        "This tool performs search based on the provided flight segments, number of passengers, trip class, currency, and locale. " \
        "It provides search_id and description of search results and saves found options internally." \
        "After receiving the result client can use `get_flight_options` tool to retrieve the found options with more granular filters." \
        "IMPORTANT: All times are local to departure/arrival locations and use HH:MM 24-hour format." \
        "IMPORTANT: Call this tool as many times as needed to find the best flight options."
    )
  • Helper function to generate the MD5 signature required for authenticating the API request according to Travelpayouts specification. Used in search_flights.
    def _generate_signature(token: str, body_without_sig: Dict[str, Any]) -> str:
        """Compute MD5 signature per Travelpayouts Flight Search spec."""
    
        ordered_values = _collect_values_sorted(body_without_sig)
        base_string = ":".join([token] + ordered_values)
        print(f"Signature base string: {base_string}", file=sys.stderr)
        return hashlib.md5(base_string.encode()).hexdigest()
  • Helper function to parse the raw API response into a structured ProposalsBatchModel, setting the batch reference on each proposal. Called repeatedly in search_flights to aggregate results.
    def parse_proposals_batch(api_response: dict) -> ProposalsBatchModel:
        """
        Parse API response dictionary into ProposalsBatchModel.
        
        Args:
            api_response: Dictionary containing the API response
            
        Returns:
            ProposalsBatchModel instance with flattened flight data
        """
        batch = ProposalsBatchModel(**api_response)
        for i in range(len(batch.proposals)):
            batch.proposals[i].batch_ref = batch
        return batch

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.5/5.0
Behavior4/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 discloses that search results are saved internally, that all times are local and in HH:MM 24-hour format, but does not mention auth or rate limits. It is fairly transparent, but could include more about statefulness.

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

Conciseness4/5

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

The description is well-structured with clear sections and front-loaded purpose. It is slightly verbose but each sentence provides value, earning a 4.

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

Completeness4/5

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

Given the complexity of the nested schema and the lack of an output schema, the description adequately explains the workflow (search then retrieve options) and provides critical usage details. It is practically complete for an agent to use effectively.

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

Parameters4/5

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

Although the schema_description_coverage is 0% (likely a metric artifact), the input schema has detailed descriptions for all nested properties. The tool description adds value with examples and important usage hints, going beyond what the schema provides. Baseline 3 is exceeded.

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

Purpose5/5

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

The description clearly states it searches for flights using Aviasales API, provides a search_id, and saves options internally. It explicitly mentions the subsequent use of get_flight_options, distinguishing itself from its siblings.

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

Usage Guidelines5/5

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

It provides explicit guidance on when to call this tool (as many times as needed) and when to use get_flight_options. It includes examples for one-way, round trip, multi-city, and warns against using multiple segments for alternative dates.

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