Google Flights as a tool for AI agents (MCP bridge)
side-flight demonstrates strong schema discipline and detailed descriptions for core tools (search, resolve_place). Tool names are action-oriented (search, resolve, refine). However, the 'refine' tool lacks a visible description in the provided source, and parameter descriptions, while present for search/resolve_place, are verbose and include example values (discouraged pattern). Output schemas are well-defined with ENVELOPE_SCHEMA. Error handling is mentioned in descriptions but not formalized in code. Overall: solid foundation with room for tightening descriptions and formalizing error recovery patterns.
Upstream request spend and the policy governing it: requests used today, the daily allowance (null when the quota is disabled — that is unlimited, not zero remaining), overshoot, accounting status, operation spacing, queue depth, the estimated wait for a new operation, and the circuit-breaker state. Local and free. Read this before planning work that costs many requests, and after a budget error. Example: budget() -> {"daily_limit": null, "used_today": 39, "remaining_today": null, "accounting_status": "reconciled", "operation_spacing_seconds": 6, "next_slot_seconds": 6, "circuit": {"state": "closed"}}
What this backend can actually do right now, per feature and per engine, with the evidence behind each claim. Local and free: it never contacts Google. Call it once at the start of a session and believe it over any other description — a feature absent here is unsupported no matter what another tool's text implies. Also reports the place kinds, currency and locale allowlists, limits, per-tool upstream cost, active ports and effective configuration. Example: features() -> {"features": {"search.one_way": {"supported": true, "supported_engines": ["page"], ...}, "explore.destinations": {"supported": false, ...}}, "currency_allowlist": ["MYR","SGD","USD"]}
Turn free text into place candidates — airports, cities and countries. Local data and previously resolved places answer for free; a miss asks Google and COSTS ONE UPSTREAM REQUEST, then remembers the answer permanently, so any country works without anyone editing a list. A place you have not looked up yet is simply unresolved, never unsupported. Ambiguous text returns several candidates and no session; you pick, then pass the `id` straight to `search`. Read `searchable`: a place can resolve and still not be searchable, because Google addresses cities and countries by its own place id and only verified ones are recorded. When it is false, `search_note` says why. The dataset is a partial seed, so no candidate means a dataset gap, not a place that does not exist. Example: resolve_place(text="Japan", kind="country") -> id "country:JP", searchable true; pass that id as a leg's `to` and search returns Japan flights. resolve_place(text="Vietnam") -> resolves, searchable false, with the reason.
refine tool has no visible description or schema in source code. Tool definition is incomplete.
Parameter descriptions include example values (e.g., 'e.g. 2026-11-12', 'e.g. KUL') which LLMs tend to reuse literally. Replace with formal constraints (enums, patterns, format declarations).
Error handling is described in tool descriptions but not formalized in code. No explicit error classification (retryable, user-fixable, fatal) or recovery guidance visible.
resolve_place and search descriptions are verbose (>300 chars) and bury key details. Descriptions should be 10 - 1024 chars; these approach the upper bound and risk token waste.
Inferred effective spec: 2025-06-18+.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 62 | 2025-06-18+ | v2 |
Search Google Flights for exact airports on exact dates and get back the equivalent of the results page: every observed row, a session handle, an immutable snapshot id, a shareable results URL, the observation time, coverage and what to do next. COSTS ONE UPSTREAM REQUEST unless the identical query is cached (then cache_hit is true and nothing is counted); fresh=true always costs one. Exact dates are required — this backend never invents a travel window. Rows arrive as data.unclassified_flights with grouping "unavailable": the page exposes two groups but not which is Best and which Cheapest, so neither label is applied. A leg may name an airport, a city or a country. For anything broader than an airport, read data.geography: it states the place you asked for beside the destination airports actually returned, and sets exhaustive=false. A country search returns what Google showed for that country, which is not a search of every airport in it. Example: search(query={"trip": "round_trip", "legs": [{"from": ["KUL"], "to": ["HND"], "date": "2026-11-12"}, {"from": ["HND"], "to": ["KUL"], "date": "2026-11-19"}], "currency": "MYR"}) -> a results view whose rows carry price, currency, price_basis, stops, carriers and segments, plus search_url for sharing.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint) present. All tools are read-only but this is not formally declared.