LetsFG
LetsFG is a flight search and booking server connecting to 200+ connectors across 400+ airlines worldwide, enabling AI agents to search, unlock, and book real flights at raw airline prices with zero markup.
Search Flights: Search live flight availability and prices across 400+ airlines, with support for one-way/round-trips, cabin class, multi-passenger, currency selection, and automatic multi-airport city expansion (e.g., LON → LHR, LGW, STN).
Resolve Locations: Convert city or airport names to IATA codes (e.g., "London" → LHR, LGW, STN).
Unlock Flight Offer: Confirm the latest live price and reserve a selected offer for 30 minutes before booking.
Book a Flight: Create a real airline reservation (PNR) at the raw ticket price via Stripe, with idempotency key support to prevent double-bookings.
Setup Payment: Attach a Stripe payment card once to enable booking (required for GDS flights).
Start Checkout: Automate airline checkout (Ryanair, Wizz Air, EasyJet) up to the payment page — never submits payment; returns a screenshot and URL for manual completion.
Link GitHub: Star the LetsFG repo to unlock free unlimited access to search, unlock, and booking.
Get Agent Profile: View account profile, payment status, and usage statistics (searches, unlocks, bookings, fees).
System Info: Retrieve RAM/CPU details and recommended concurrency settings for optimal search performance.
Enables searching and booking flights from AirAsia via local airline connectors.
Provides flight search and booking for All Nippon Airways (ANA) through enterprise GDS/NDC sources.
Allows searching and booking flight offers from British Airways using enterprise data sources.
Supports real-time flight search and booking for easyJet using local airline connectors.
Enables retrieval of flight offers and booking for Emirates via enterprise GDS/NDC connections.
Resolves city and airport names into standardized IATA codes to facilitate travel searches.
Provides tools for searching and booking flights across Lufthansa's global network.
Allows searching and booking flights from Norwegian via local airline connectors.
Enables searching and booking of Ryanair flights, supporting low-cost travel and virtual interlining.
Provides access to flight inventory and booking for Singapore Airlines via enterprise sources.
Supports search and booking for Wizz Air flights via local airline connectors.
We're LetsFG — a community of travelers.
Finding a flight or hotel shouldn't mean checking 47 tabs. Or 3 hours of searching. Or having that feeling you could've got a better deal if you'd just waited a little longer.
So we built something about it. No markup. No tracking. No price that goes up because you looked twice.
Join the community. Help others find cheaper flights. Spread the word.⭐ Star the repo. Share with a friend ✈️
Flights and hotels. Both live.
Hundreds of airlines. Real prices. One function call.
LetsFG gives your AI agent flight and hotel search and booking superpowers. Our server-side engine scans the entire world for the cheapest price. Search is free. Booking is real: the fare is held on your card, a LetsFG booking agent buys the ticket, and you get the airline's PNR.
The same flight costs $20–$50 less because you skip OTA inflation, cookie tracking, and surge pricing.
Agents: add https://letsfg.co/developers/api/mcp as an MCP server. Approving the connection opens letsfg.co/connect, where you add a card in a 0.00 Revolut setup (nothing is charged, no Revolut account needed). That token searches for free and books. Scripts: send the same token as Authorization: Bearer to the PFS endpoints. Developer API: a separate paid product for high-volume commercial use; most agents do not need it. → Get started
Supporters
Real prices: LetsFG vs Google Flights
We searched 5 routes on Google Flights and LetsFG on the same day (2026-08-05), for flights departing 2026-09-16. Same airline, same number of stops — LetsFG was cheaper every time:
Route | Airline | Google Flights | LetsFG | You Save |
LAX → Paris (CDG) | JetBlue, 1 stop | $363 | $334 | $29 |
SFO → London (LHR) | JetBlue, 1 stop | $349 | $333 | $16 |
LA → New York (JFK) | JetBlue, nonstop | $174 | $157 | $17 |
London → Singapore (SIN) | Shenzhen Airlines, 1 stop | $395 | $380 | $15 |
Chicago → Dubai (DXB) | Air Canada + Emirates, 2 stops | $517 | $461 | $56 |
$133 cheaper across 5 routes in a verified comparison (2026-08-05). Google Flights inflates on repeat searches; LetsFG returns the same prices however often you run the search, because it reads airlines and the major booking sites directly rather than tracking you.
Why the difference? Google Flights only searches its own limited set of airline partners. LetsFG searches everywhere — Skyscanner, Kiwi, Kayak, Momondo, plus direct airline websites (Ryanair, United, Southwest, EasyJet, Spirit, Norwegian, AirAsia, and hundreds more). More sources = better prices. No demand-based inflation and no cookie tracking: the same search returns the same prices however often you run it.
Related MCP server: Flights MCP
Real hotel prices: LetsFG vs Booking.com
Same hotel, same room type, same 2-night stay, same free-cancellation policy — checked on the same day (2026-08-05) for a 2026-09-16 check-in:
Hotel | Booking.com | LetsFG | You Save |
Hotel Boss, Warsaw | $206 | $169 | $37 |
ibis Styles Paris Gare de l'Est | $663 | $525 | $138 |
Copthorne Tara Hotel, London Kensington | $379 | $347 | $32 |
$207 cheaper across 3 hotels in a verified comparison (2026-08-05), matching each property's own free-cancellation rate against Booking.com's free-cancellation rate for the identical dates and room type. Prices quoted in PLN at booking, converted to USD at that day's rate.
Why the difference? LetsFG sells at wholesale cost — no markup for demand, no loyalty-program cross-subsidy. You're not paying for the room upfront: 5% books it now, and the remaining 95% isn't due until the hotel's own cancellation deadline. Cancel before that deadline and you lose nothing but the 5%; the rest was never charged. Only free-cancellation, pay-later rates are sold, so every price shown is one you can actually hold risk-free.
Try it right now — no install needed
Human users: Use letsfg.co and search flights instantly in your browser:
🌐 Search on letsfg.co
Search any route, compare live results, and book the flights you want — no installation needed.
Agents / scripts (free server-side): Get a Bearer token by putting a payment method on file (nothing is charged) → use POST /api/search and POST /api/agent-book. This is PFS — Programmatic Flight Search powered by the letsfg.co engine. Search is free; the token is short-lived and refreshes itself. See letsfg.co/for-agents for the full guide.
When you're ready to integrate it into your own agent, keep reading.
Three ways to use LetsFG
Path 1 — MCP / CLI / SDK | Path 2 — PFS (Programmatic Flight Search via letsfg.co) | Path 3 — Developer API | |
Best for | AI agents (Claude, ChatGPT, Cursor, Windsurf), personal use — easiest way in | Scripts/agents calling the API directly with a Bearer token | High-volume commercial integrations that want prepaid billing. Most agents should not use this |
Speed | 8–10 s to first results | 8–10 s to first results | 2–5 s (discover) · 8–10 s to first results (full search) |
Search cost | Free (card connected once, nothing charged) | Free (card connected once, nothing charged) | Look-to-book: 200 free after every booking, then $0.01 |
Booking |
|
|
|
Setup | Add | Same token, sent as | |
Runs where | Our servers (ranking local in the SDK) | Our servers | Our servers |
MCP / CLI / SDK (Path 1): add
https://letsfg.co/developers/api/mcpas an MCP server in Claude, ChatGPT, Cursor or Windsurf and approve the connection. The consent step opens letsfg.co/connect, where the person adds a card (any card, or Revolut Pay / Google Pay) in a 0.00 Revolut setup: nothing is charged, no Revolut account is needed, and card details go to Revolut, never to LetsFG. The token you get back is card-backed: it searches for free and it can book. The Python and JS SDKs read that token fromLETSFG_BEARER_TOKENor~/.letsfg/config.jsonand apply the open-source ranking algorithm locally. (letsfg authruns this same connect flow from the terminal: it registers itself as an OAuth client, opens the card screen in a browser for a person to approve, and stores the token.)PFS — Programmatic Flight Search (Path 2): For scripts and agents that call the API directly. letsfg.co is human-only by default (Cloudflare Turnstile + bot protection), so the card-backed token from the connect flow is the only programmatic way in. Send it on every request:
Search:
POST https://letsfg.co/api/searchwithAuthorization: Bearer <token>→{ search_id }Poll:
GET https://letsfg.co/api/results/<search_id>(never counts against the rate limit)Book:
POST https://letsfg.co/api/agent-book→{ booking_ref }within secondsWait:
POST https://letsfg.co/api/agent-book/statuswith{ booking_ref }every 20–30 s untilcompleted(PNR),failed(hold released, nothing charged) orneeds_attention
POST /api/agent-access/requeststill answers402withadd_card_urland these steps as JSON, so an agent that starts from the endpoint lands in the same place. The MPP lane (a wallet, no card) is unchanged: answer theWWW-Authenticate: Paymentchallenge ($0.01 once) and verify withAuthorization: Payment. The Stripesetup_url/ SetupIntent lanes were retired on 2026-09-02 and every token they issued was revoked; reconnect at letsfg.co/connect. Full guide and response schema: letsfg.co/for-agents.Developer API (Path 3): Server-side search and booking at letsfg.co/developers. Look-to-book search (200 free after every booking, then $0.01), real booking through
POST /flights/bookon a connected Revolut method, full NL query parsing, a/discoverendpoint that checks 20 destinations in one call (2–5 s), hotels, and a free sandbox at/sandbox/flights/*. Full docs: letsfg.co/developers/api/docs.
Free server-side search: Use Path 1 or PFS — connect a card once at letsfg.co/connect (nothing charged) and searches run free on our servers. No Playwright, no local install beyond the SDK. Booking from your own product: Use the Developer API (Path 3) — look-to-book search,
POST /flights/book, and no booking or transaction fee. It is also the only path to hotels.
Pricing
How you use it | Search | Flight booking | Hotel booking | Runs where? |
MCP Server | ✅ Free (card connected once at letsfg.co/connect) | Fare + markup held, captured on a real PNR. No separate fee | 5% reservation fee | Our servers |
CLI / Python SDK / npm | ✅ Free (same token) | Same | 5% non-refundable reservation fee | Our servers |
PFS (raw API via letsfg.co) | ✅ Free (same token, or $0.01 once via MPP) | Same | 5% reservation fee | Our servers |
Developer API | 200 free per booking, then $0.01 | Fare + markup held, captured on a real PNR. No booking fee, no transaction fee | 5% reservation fee | Our servers |
MCP / CLI / SDK / PFS = free search, real booking, no separate fee. Connect a card once (a 0.00 Revolut setup, nothing is charged) and searching is free. No credits, no unlock step. Booking works exactly like the website checkout: book_flight / POST /api/agent-book holds the fare plus LetsFG's markup on your card, a LetsFG booking agent buys the ticket from the seller, and the hold is captured only once a real airline PNR exists. If the booking fails the hold is released and nothing is charged. The price you see is the price you pay; the markup is inside it, nothing is added at booking.
Hotels = 5% now to hold a free-cancellation rate, on every path. That 5% is what pays for flexibility: it books the room today, but the remaining 95% isn't charged until the hotel's own cancellation deadline, paid straight to the hotel via a pay_link. Cancel before that deadline and the only cost is the 5% already paid. See Hotels above.
Developer API = business use, look-to-book. letsfg.co/developers books flights itself — POST /flights/book holds the fare on your connected Revolut method and a LetsFG booking agent buys the ticket, exactly like the other paths. Search is not priced per call: you get 200 free searches after every booking you make, and booking resets the counter. Past that, blocks of 500 for $5.00 ($0.01 each). No booking fee, no transaction fee — the margin is inside the price the search returned, so the amount shown is the amount charged. Minimum top-up: $5.
The monthly per-search tiers ($0.50 / $0.20 / $0.10) were retired on 2026-09-08 along with Stripe. Payments are Revolut:
POST /agents/connect-paymentreturns a one-time link that saves a card, and nothing is charged to connect.
💡 Know someone who travels? The more people discover LetsFG, the more airlines we cover — and the better it gets for everyone. ⭐ Star · Share with a friend
Why developers star this repo
Google Flights / Expedia | LetsFG | |
Price | Inflated (tracking, cookies, surge) | Stable across repeat searches. $133 cheaper across 5 routes, verified 2026-08-05. |
Coverage | Misses budget airlines | Hundreds of airlines — OTAs, budget carriers, full-service |
Speed | 30 s+ (page loads, ads, redirects) | CLI/PFS: 8–10 s to first results · API discover: 2–5 s |
Repeat search raises price? | Yes | Never |
Works in AI agents? | No API | MCP · CLI · PFS (card connected once, free) · Developer API (prepaid) |
Booking | Redirects to OTA checkout | Real airline PNR, e-ticket to inbox |
Cabin class filter | No | Economy, premium, business, first |
Cost to you | Hidden markup | CLI/PFS: free search; no booking fee and no transaction fee. Developer API: 200 free searches after every booking, then $0.01/search; no booking fee, no transaction fee. |
Get started
Everything runs on our servers. One card connection covers the MCP, the SDKs and the raw API.
🔌 MCP — connect once, search and book
Add the remote server and approve the connection. The consent step opens letsfg.co/connect: add a card (or pay 0.00 with Revolut Pay / Google Pay), nothing is charged, and you are in.
# Claude Code
claude mcp add --transport http letsfg https://letsfg.co/developers/api/mcp// Cursor (~/.cursor/mcp.json) — Windsurf uses "serverUrl" instead of "url"
{ "mcpServers": { "letsfg": { "url": "https://letsfg.co/developers/api/mcp" } } }claude.ai and ChatGPT: add a custom connector with the same URL.
Then, in the chat: "find me the cheapest flight from London to Barcelona on June 15 and book it". search_flights returns offers; book_flight holds the fare on your card and starts a LetsFG booking agent; get_flight_booking reports the PNR when it lands (4–11 minutes).
🖥️ CLI / SDK — same token, ranking runs locally
pip install letsfg
export LETSFG_BEARER_TOKEN=<token from the connect flow>
letsfg search LHR BCN 2026-06-15
letsfg search LHR JFK 2026-06-15 --cabin C # cabin class: M economy, W premium, C business, F firstThe SDKs read the token from LETSFG_BEARER_TOKEN or ~/.letsfg/config.json. letsfg auth performs that connect flow itself: it registers as an OAuth client, opens https://letsfg.co/connect for a person to approve, and writes the token to ~/.letsfg/config.json. Add --no-browser to print the URL instead of opening one.
🔌 PFS — Programmatic Flight Search (free, server-side)
Run LetsFG's full search on our servers from any script. Access requires a card connected to a LetsFG account: letsfg.co is human-only (Cloudflare Turnstile), so the card-backed token from the connect flow is the only programmatic way in. Nothing is charged to connect it.
# 1. Search with the token
curl -X POST https://letsfg.co/api/search \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"origin":"LHR","destination":"BCN","date_from":"2026-06-15"}'
# → {"search_id":"ws_abc123","status":"searching"}
# 2. Poll until done (keep going while split_ticket_pending is true)
curl https://letsfg.co/api/results/ws_abc123 -H "Authorization: Bearer <token>"
# 3. Book — the fare is HELD on the card, a LetsFG agent buys the ticket
curl -X POST https://letsfg.co/api/agent-book \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"search_id":"ws_abc123","offer_id":"wo_abc123","contact_email":"ada@example.com",
"passenger":{"given_name":"Ada","family_name":"Lovelace","born_on":"1990-04-01","gender":"f",
"nationality":"GB","phone_number":"+447700900000","phone_country":"GB",
"address_line1":"1 Analytical Way","address_city":"London","address_postal":"N1 9GU","address_country":"GB"}}'
# → {"booking_ref":"eyJ..."} within seconds
# 4. Wait for the PNR (every 20–30 s; a booking takes 4–11 minutes)
curl -X POST https://letsfg.co/api/agent-book/status \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d '{"booking_ref":"eyJ..."}'
# → {"state":"completed","pnr":"ABC123","charged_amount":93,"currency":"EUR"}failed means the hold was released and nothing was charged; needs_attention means a human at LetsFG is checking it, do not book again. A missing passenger detail returns missing_fields and charges nothing. Without a card the endpoint answers payment_method_required with add_card_url. Full guide and response schema: letsfg.co/for-agents.
⚡ Developer API — server-side search, booking and hotels
A separate product for high-volume commercial integrations; most agents should not use it. Look-to-book search, results in seconds, and real booking through POST /flights/book — plus /discover (20 destinations in one call), async polling, NL query parsing, hotels, and a free sandbox.
# Register, then search with your API key
curl -X POST https://letsfg.co/developers/api/v1/agents/register \
-H "Content-Type: application/json" \
-d '{"agent_name":"my-agent","email":"you@example.com"}'
curl -X POST https://letsfg.co/developers/api/v1/flights/search \
-H "X-API-Key: letsfg_..." \
-H "Content-Type: application/json" \
-d '{"origin":"LHR","destination":"BCN","date_from":"2026-06-15"}'Pricing: 200 searches free after every booking, then blocks of 500 for $5.00 ($0.01 each). No booking fee, no transaction fee. Minimum top-up $5. Test for free in the sandbox first. Full docs: letsfg.co/developers/api/docs.
search_flights LON → BCN, 2026-04-01, return 2026-04-08
get_flight_results (while more offers are still landing)
book_flight search_id + offer_id + one traveller's real details + contact_email
→ booking_ref in seconds; fare + markup HELD on the connected card
get_flight_booking every 20–30 s → completed (PNR, charged_amount) | failed (hold released) | needs_attentionOver raw HTTP the same four steps are POST /api/search, GET /api/results/<id>, POST /api/agent-book, POST /api/agent-book/status.
letsfg unlock was retired on 2026-09-08 and its route answers 410 Gone.
There is no unlock step on either lane: booking holds the fare and captures only
against a real PNR, which is what unlock existed to protect against. See
CLI Commands.
💡 Like what you see? Support us — ⭐ Star · Share with a friend
Works everywhere your agent runs
MCP Server (Claude / ChatGPT / Cursor / Windsurf / OpenClaw)
Use the hosted server. It carries your card-backed token for you, and book_flight books for real.
{
"mcpServers": {
"letsfg": {
"url": "https://letsfg.co/developers/api/mcp"
}
}
}Approve the connection when your client asks; the consent step opens letsfg.co/connect, where a card is added in a 0.00 setup (nothing is charged). Tools: search_flights, get_flight_results, book_flight, get_flight_booking, plus the hotel tools.
The stdio package (npx -y letsfg-mcp) still works for search if you give it a token in LETSFG_BEARER_TOKEN; its own authenticate tool points at the retired Stripe enrolment and is being moved to the connect flow.
{
"mcpServers": {
"letsfg": {
"command": "npx",
"args": ["-y", "letsfg-mcp"],
"env": {
"LETSFG_API_KEY": "letsfg_your_api_key"
}
}
}
}This is a separate paid product for high-volume commercial use — most agents should stick with the free connect flow above and skip this. A human setting this up deliberately gets a key at letsfg.co/developers.
5-minute quickstarts: Claude Desktop · Cursor · Windsurf
Python SDK
from letsfg import LetsFG
bt = LetsFG() # reads LETSFG_API_KEY from env
flights = bt.search("LHR", "JFK", "2026-04-15")
print(f"{flights.total_results} offers, cheapest: {flights.cheapest.summary()}")JavaScript SDK
import { LetsFG } from 'letsfg';
const bt = new LetsFG({ apiKey: 'letsfg_...' });
const flights = await bt.search('LHR', 'JFK', '2026-04-15');
console.log(`${flights.totalResults} offers`);Python SDK (cloud search)
from letsfg.local import search_local
# Reads LETSFG_BEARER_TOKEN env var or ~/.letsfg/config.json (the token from the connect flow)
result = await search_local("GDN", "BCN", "2026-06-15")
for offer in result.offers[:5]:
print(f"{offer.airlines[0]}: {offer.currency} {offer.price}")⇄ Split tickets — two tickets, one trip
A long-haul searched as one journey comes back as one through-fare, because everyone is reselling the same ticket. Searched as two independent legs through a hub, each leg is booked from whatever is cheapest for that hop — whichever airline, whichever seller. Usually that lands on two different airlines (often a low-cost carrier for the short leg and a separate airline for the long one), but the only rule is "cheapest for each leg". Nobody sells the combination as one ticket, so nobody quotes it — which is exactly why it is cheaper.
LetsFG builds that itinerary for you and returns it alongside the through-fares. Split offers are flagged, never disguised:
Field | Value on a split offer |
|
|
|
|
|
|
for o in offers:
if o.get("split_ticket") == "true":
print(o["price"], "— two separate tickets, self-transfer not protected")Read self_transfer before you present the price. unprotected means the
two tickets are not linked: if the first flight is late and the connection is
missed, the second airline owes nothing — no rebooking, no refund, no
duty of care. That is the trade you are being offered in exchange for the
saving, and it has to reach the traveller. We only build a split when the
connection has a real buffer, and we say so on the offer — but an
agent that relays the price without the condition is misrepresenting it.
The probe is gated. Two extra connector fan-outs cost real money, so a split is only attempted when the through-fare is expensive enough, the journey long enough, and the market has somewhere to break the trip. Most searches never fire it, and a search that fires it does not always find a saving.
It lands after completed. See
Polling: completed is not the end.
Where it runs. letsfg.co, and the agent lane behind it — the CLI,
the Python and JS SDKs, and the MCP server. The paid Developer API is served by
a different backend; self_transfer is returned there, but split-ticket offers
are not part of that contract.
What it actually saves
Route | Cheapest single ticket | Split — two tickets | You save | Connect via | Two airlines | Layover |
Stuttgart → Shanghai | $552 | $461 | $91 (16%) | Budapest | Wizz Air + Qatar Airways | 6 h |
Katowice → Dubai | $283 | $216 | $67 (24%) | Istanbul | Wizz Air + Pegasus | 15 h |
Chongqing → Gdansk | $429 | $387 | $42 (10%) | Stockholm | China Eastern + Ryanair | 10 h |
Vilnius → Bangkok | $370 | $337 | $33 (9%) | Athens | Ryanair + Air Arabia | 5 h |
Shanghai → Vilnius | $406 | $377 | $29 (7%) | London | Shenzhen Airlines + Wizz Air | 12 h |
Measured August 2026 on live searches (one adult, one way, ~2 months out). Each split is two separate tickets (here, two different airlines) with an unprotected self-transfer — if the first flight is late, the second airline owes you nothing. Prices move constantly; these won't reproduce exactly. 5 of 26 long-haul routes searched produced a split cheaper than the best single ticket — the win shows up at secondary cities with no cheap direct long-haul. Regenerate with tools/split-comparison.py.
The numbers above come from real searches and are reproducible: the script runs
the same public search anyone can run, compares the cheapest split against the
cheapest single ticket in the same result set, and writes the raw offers to
split-comparison.json so any row can be checked. Routes that produced no split
are reported too rather than dropped quietly — most searches never fire
a split probe, and a table that hid that would misrepresent how often this
happens.
✦ Starlink Wi-Fi on results
Offers tell you whether the flight has Starlink, in two honest tiers. A solid
verdict (confirmed_all) means the airline has fitted every aircraft of that
type. A hedged one (likely_all) means the rollout on that type is real but
incomplete — as of August 2026 United was ~29% of its fleet — so it is reported
as a signal, never a promise. An absent field means no information, not an
absence of Wi-Fi.
for o in offers:
if o.get("starlink") == "confirmed_all":
print(f"{o['owner_airline']} {o['price']} — Starlink on every leg")Full semantics: docs/api-search.md.
🏨 Hotels — new, and live
Your agent can now book hotels, not just flights. Same API key, same card on file.
from letsfg import LetsFG
lfg = LetsFG()
city = lfg.hotel_destinations("Warsaw")[0]
stays = lfg.search_hotels(
city_id=city["Id"], city_name=city["Name"],
check_in="2026-11-10", check_out="2026-11-12", adults=2,
)
hotel = stays["hotels"][0]
offer = hotel["offers"][0]
print(hotel["name"], offer["price"], stays["currency"])
# Hotel Gromada Warszawa Centrum 669.86 PLN
booking = lfg.book_hotel_and_wait(
session_id=stays["session_id"],
hotel_code=hotel["hotel_code"],
combination_id_v2=offer["combination_id_v2"],
expected_price=offer["price"],
expected_balance=offer["balance_to_supplier"],
city_id=city["Id"], city_name=city["Name"],
check_in="2026-11-10", check_out="2026-11-12",
guests=[{"title": "Mr", "first_name": "Jan", "last_name": "Kowalski"}],
email="guest@example.com", phone="512345678",
)
print(booking["confirmation"], booking["pay_link"])How you pay
5% now, the rest to the hotel later. At booking we charge 5% of the price
to your card as a reservation fee. The remaining balance is paid directly to
the supplier through a pay_link we return — we never hold it.
balance_due_by is the supplier's own auto-cancellation date, not a date we
invent. Miss it and the room is released.
The 5% is non-refundable. Cancelling before balance_due_by costs nothing
else; after it, the hotel's own cancellation ladder applies and can reach 100%.
That ladder ships in the booking's terms, so you can always see the cost before
you cancel.
Things worth knowing before you build
A card on file is required for every hotel call, including search. That is unusual and it is deliberate: a hotel search opens a real session at the supplier, and booking blocks a real rate. We would rather refuse up front than let you reach the point of commitment and discover you cannot pay. The same card that authorises flight booking authorises hotels — there is no separate hotel signup.
Only free-cancellation, pay-later rates are sold. Those are the rates where the balance can safely be settled with the supplier after booking, which is what makes 5%-now/rest-later work at all. You will see fewer results than a metasearch shows you. Every one of them can actually be booked.
Booking is asynchronous.
book_hotelreturns abooking_job_id, not a booking — the real thing takes minutes. Pollhotel_booking(job_id)untilstatusissucceededorfailed, or callbook_hotel_and_waitand let the SDK do it. This is not ceremony: it is what makes it impossible to charge a card and then lose the confirmation to a timeout.The fee is charged before the room is committed. A declined card therefore costs nothing to unwind — no reservation exists and nothing is charged.
Do not retry a booking blindly. Calling
book_hoteltwice for the same rate books the room twice and charges two reservation fees.priceis what the guest pays. There is no wholesale figure in the response to quote by mistake.
JavaScript
import { LetsFG } from 'letsfg';
const lfg = new LetsFG({ apiKey: process.env.LETSFG_API_KEY });
const [city] = await lfg.hotelDestinations('Warsaw');
const stays = await lfg.searchHotels({
cityId: city.Id, cityName: city.Name,
checkIn: '2026-11-10', checkOut: '2026-11-12', adults: 2,
});
const booking = await lfg.bookHotelAndWait({ /* ...offer + guest details... */ });
console.log(booking.confirmation, booking.pay_link);MCP
Five new tools, in the order you call them: resolve_hotel_city →
search_hotels → book_hotel → get_hotel_booking → cancel_hotel_booking.
🖥️ Omarchy desktop plugin
Search hundreds of airlines from the Omarchy bar. Type
two airport codes and a date, press Search, click an offer to open it. The
panel lives in this repo — manifest.json, BarWidget.qml, Panel.qml and
Model.js at the root — and runs on the same engine as the CLI and the MCP
server, ordered by the same open-source ranking algorithm in sdk/js/src/ranking.ts.
Install
omarchy plugin add https://github.com/LetsFG/LetsFG.git --enableThen add LetsFG Flights to a bar section in the Omarchy bar settings. The
panel reads your LetsFG token from ~/.letsfg/config.json (what letsfg auth
writes) and renews it itself, or connects a card from the panel via
letsfg.co/connect — nothing is charged. See
OMARCHY-PLUGIN.md.
Remove
omarchy plugin remove io.github.letsfg.flightsThat removes the plugin only. Your token is yours — delete
~/.letsfg/config.json yourself if you want it gone.
The plugin bundles no API keys. It authenticates with a token you create and can revoke, it never asks for card details, and it never starts a search on its own — no background poll, no price watch, no refresh timer. Every host it can contact, every file it reads or writes, and the anonymous installation id it sends so we can tell whether anyone is using it, are documented in full in OMARCHY-PLUGIN.md.
Requires the Omarchy Quattro shell. MIT, like the rest of this repo. Not affiliated with, sponsored by, or endorsed by Omarchy or 37signals.
Install
Package | Command | What you get |
Remote MCP |
| No install. Approve the connection, add a card at letsfg.co/connect, search and book |
Python SDK + CLI |
| SDK + CLI (token from the connect flow in |
MCP Server (stdio) |
| Local server for clients without remote MCP support; needs |
JS/TS SDK |
| SDK + CLI + open-source ranking engine |
Agent Skill |
| Install flight search skill for any AI agent (skills.sh) |
Smithery | One-click MCP install | |
Omarchy plugin |
| Flight search in the Omarchy bar (details) |
CLI Commands
Command | Description |
| Connect a card at letsfg.co/connect and store the token (self-registers, PKCE + loopback redirect, opens a browser). |
| Search flights (free with a card-backed token) |
| [Developer API only] Register an account for the paid, prepaid-credit product — not part of the agent flow |
| RETIRED 2026-09-08 with Stripe — the route answers |
| Recover lost API key via email |
| Resolve city/airport to IATA codes |
| RETIRED 2026-09-08 — the route answers |
| Book the flight: holds the fare on the connected card, a LetsFG agent buys the ticket, returns a |
| View profile & usage stats |
All commands accept --json for structured output and --api-key to override the env variable.
How it works
CLI / SDK / MCP (free, cloud-backed)
Connect the MCP (once, card added at letsfg.co/connect) → card-backed token → Search (free) → Book (hold → agent → PNR)Auth — add
https://letsfg.co/developers/api/mcpas an MCP server and approve it. The OAuth consent step opens letsfg.co/connect, where a card is added in a 0.00 Revolut setup. Nothing is charged. The SDKs read that token fromLETSFG_BEARER_TOKENor~/.letsfg/config.json.Search —
letsfg search LHR BCN 2026-06-15callsPOST https://letsfg.co/api/search, polls until done (8–10 s to first results), and applies the open-source ranking algorithm locally.Book —
POST /api/agent-bookholds the fare plus markup on the card and starts a LetsFG booking agent;POST /api/agent-book/statusreportscompletedwith the PNR (4–11 minutes), orfailedwith the hold released. Nothing extra is added at booking.
Polling: completed is not the end
A search returns in 8–10 s to first results. Poll GET /api/results/<search_id>
immediately and then every 2 s — a loop that sleeps first puts a
floor under a search that is already faster than the sleep.
When status leaves searching, the connector fan-out is done — but
the offer set may still be growing. The split-ticket probe is dispatched after
the fan-out and merges its result in late, so the cheapest itinerary on the
search is routinely one that does not exist yet at the moment the status turns
terminal. The response says so:
Flag | Meaning while |
| a split-ticket probe is still running |
| the Google Flights enrich has not merged yet |
Keep polling while either is true, and bound the wait — a flag that never clears must not hang your agent. Take whatever has landed when the bound expires.
The Python and JS SDKs and the MCP server already do this, with a 90 s ceiling
— the same window the server uses to decide a result has settled.
So a search that fires a split probe can take meaningfully longer than the
8–10 s to first results fast path, and it is the split offer you are waiting for.
Set LETSFG_WAIT_FOR_SPLIT=0 if you would rather have the fast answer.
Most searches never fire the probe, so both flags are usually already false on the first poll and this costs nothing.
PFS — raw API (same as CLI, without the wrapper)
Card connected at letsfg.co/connect -> Bearer token -> POST /api/search -> poll GET /api/results/<id> -> POST /api/agent-book -> poll POST /api/agent-book/statusGet a Bearer token — connect through the MCP OAuth flow; the consent step is letsfg.co/connect (card or Revolut Pay, 0.00, nothing charged).
POST /api/agent-access/requestanswers402withadd_card_urland the steps. The MPP wallet lane ($0.01 once) verifies atPOST /api/agent-access/verifywithAuthorization: Payment.Search —
POST https://letsfg.co/api/searchwithAuthorization: Bearer <token>. Returns{ search_id }. PollGET /api/results/<search_id>immediately, then every 2 s, untilstatusleavessearching. Then keep polling whilesplit_ticket_pendingorgf_enrich_pendingis true — the split-ticket offer merges in after the status turns terminal.Book —
POST /api/agent-book→booking_ref; pollPOST /api/agent-book/statusuntilcompleted(PNR) orfailed(hold released, nothing charged).
Developer API — server-side search and booking
Register → Connect a Revolut method → Search (200 free per booking) → POST /flights/book → poll to a PNRDiscover —
POST /flights/discoverwith up to 20 destinations, get indicative prices sorted cheapest-first. 1 credit, 2–5 s. Use to rank options before committing to a full search.Full search —
POST /flights/search(blocking) or/flights/search/async(non-blocking + poll). 1 credit, 8–10 s to first results.Book — each offer includes a direct airline
booking_url. No LetsFG fee, no checkout step.
The server-side engine builds cross-airline round-trips by combining one-way fares from different carriers. A Ryanair outbound + Wizz Air return can save 30-50% vs booking a round-trip on either airline alone.
Search a city code and LetsFG automatically searches all airports in that city. LON expands to LHR, LGW, STN, LTN, SEN, LCY. NYC expands to JFK, EWR, LGA. Works for 25+ major cities worldwide.
Architecture
CLI / SDK / MCP / PFS
CLI / SDK / MCP / AI Agent
│ Card connected at letsfg.co/connect -> card-backed Bearer token
▼
POST letsfg.co/api/search (bot-protected, token required)
│
▼
letsfg.co server-side search engine
│
▼
GET /api/results/<search_id> (poll every 2 s; keep going while split_ticket_pending)
│
▼
Ranking applied locally (sdk/js/src/ranking.ts, open-source)
│
▼
Results + booking via POST /api/agent-book (hold on card -> LetsFG agent -> PNR)Developer API
Product / Team / Agent
│ API key + a connected Revolut method
▼
letsfg.co/developers/api/v1
├─ /flights/discover (indicative prices, 20 dest, 2–5 s)
├─ /flights/search (full search, 8–10 s to first results)
├─ /flights/search/async (non-blocking + poll)
├─ /flights/parse-query (Gemini NL parsing, free)
├─ /flights/book (holds the fare on the connected method)
├─ /flights/bookings/{id} (poll to a real PNR, 4–11 min)
└─ /sandbox/flights/* (fake data, same schema, free)
│
▼
Real airline PNR - the hold is captured only once it existsRegion | Airlines |
Europe | Ryanair, Wizz Air, EasyJet, Norwegian, Vueling, Eurowings, Transavia, Pegasus, Turkish Airlines, Condor, SunExpress, Volotea, Smartwings, Jet2, LOT Polish Airlines, Finnair, SAS, Aegean, Aer Lingus, ITA Airways, TAP Portugal, Icelandair, PLAY |
Middle East & Africa | Emirates, Etihad, Qatar Airways, flydubai, Air Arabia, flynas, Salam Air, Air Peace, FlySafair, EgyptAir, Ethiopian Airlines, Kenya Airways, Royal Air Maroc, South African Airways |
Asia-Pacific | AirAsia, AirAsia X, IndiGo, SpiceJet, Akasa Air, Air India, Air India Express, Alliance Air, Star Air, EaseMyTrip OTA, VietJet, Cebu Pacific, Scoot, Jetstar, Peach, Spring Airlines, Lucky Air, 9 Air, Nok Air, Batik Air, Jeju Air, T'way Air, ZIPAIR, Skymark, H.I.S. Travel OTA, Singapore Airlines, Cathay Pacific, Malaysian Airlines, Thai Airways, Korean Air, ANA, JAL, Qantas, Virgin Australia, Bangkok Airways, Air New Zealand, Garuda Indonesia, Philippine Airlines, US-Bangla, Biman Bangladesh |
Americas | Southwest, JetBlue, Frontier, Spirit, Allegiant, Avelo, Breeze, Sun Country, Flair, Porter, WestJet, Volaris, VivaAerobus, GOL, Azul, LATAM, JetSmart, Flybondi, Arajet, Wingo, Sky Airline, Copa, Avianca |
Oceania | Rex, Bonza, Link Airways, Air Vanuatu, Fiji Airways |
Star History
letsfg.co · API Docs · Connector Health · PyPI · npm · Smithery · Instagram · TikTok · X
Open source · MIT License · Made with ❤️ by travelers, for travelers
Want updates? Click Watch above, or follow LetsFG on Instagram, @letsfg_ on TikTok, or @LetsFG_ on X.
Available Tools
13 toolsauthenticateAInspect
Get a LetsFG token by putting a payment method on file. Nothing is charged — a zero-amount Stripe setup, no charge and no authorization hold. Call once.
Call with no arguments to start: returns a setup_url for the user to add a card, plus a setup_session_id. Call again with that setup_session_id once they are done to receive the token. For a fully headless enrolment, mint a single-use card token against the LetsFG publishable key and pass card_token instead, skipping the browser entirely. payment_method_id is accepted ONLY for a card already enrolled through this flow — a bare pm_ id is not proof of card control.
This does NOT create a Developer API billing account. Do not use setup_payment for this.
| Name | Required | Description | Default |
|---|---|---|---|
| card_token | No | Single-use Stripe tok_... minted against the LetsFG publishable key — the headless path | |
| setup_session_id | No | cs_... from a previous authenticate call, after the card was added | |
| payment_method_id | No | Stripe pm_... for a card ALREADY enrolled through this flow (re-issue only) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden and does so: discloses "Nothing is charged — a zero-amount Stripe setup, no charge and no authorization hold," explains the two-step token flow, and states restrictions on payment_method_id (only for cards already enrolled).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Every sentence earns its place: the description is dense but logically ordered, covering outcome, safety, call flow, alternate path, and exclusions without fluff. It is front-loaded with the primary purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description explains return values (setup_url, setup_session_id, token) and covers edge cases (headless, re-issue, exclusion of billing account). Given the complexity of a multi-step authentication flow, this is complete and actionable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds significant meaning beyond schema: card_token is the headless path, setup_session_id comes from a previous call, and payment_method_id is only for re-issuing tokens for already enrolled cards. This clarifies usage context not present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource+outcome: "Get a LetsFG token by putting a payment method on file." It clearly distinguishes from the sibling tool setup_payment by explicitly stating "Do not use setup_payment for this."
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit step-by-step usage: first call with no arguments, then call again with setup_session_id, or use card_token for headless enrollment. Also gives a clear exclusion: "This does NOT create a Developer API billing account. Do not use setup_payment for this."
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
book_flightAInspect
Book a flight from a search result.
FLOW: authenticate (once) -> search_flights -> book_flight CHARGES: nothing from LetsFG. A completed booking pays the airline prices from airlines and the major booking sites. RESULT: either {"booked": true, "order_id": "..."} or {"booked": false, "booking_url": "..."} — the second means the booking genuinely did not complete and nothing was charged. That is a normal outcome, NOT a transient error: do not retry, give the user the booking_url. SAFETY: use REAL passenger details — names must match passport, email receives the e-ticket.
| Name | Required | Description | Default |
|---|---|---|---|
| offer_id | Yes | Offer ID from search_flights | |
| search_id | Yes | search_id from search_flights | |
| passengers | Yes | Passengers with 'id' from search passenger_ids | |
| contact_email | Yes | Booking contact email | |
| idempotency_key | No | Unique key to prevent double-bookings on retry (e.g., UUID). Strongly recommended. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and excels. It discloses charges ('nothing from LetsFG'), the two result variants (success with order_id, incomplete with booking_url), and warns that incomplete is a normal, non-retryable outcome. It also requires 'REAL passenger details' and explains why (names match passport, email receives e-ticket), providing safety context not inferable from schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact (~130 words) and uses labeled sections (FLOW, CHARGES, RESULT, SAFETY) to front-load critical information. Every sentence earns its place, with no filler or repetition of schema content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
It covers flow, charges, result handling, and safety requirements. The result handling for incomplete bookings is exactly the kind of edge-case detail an agent needs, and the absence of an output schema is compensated by explicit example JSONs. No significant gaps remain for the agent to execute the booking safely and correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds meaningful context: 'passengers with id from search' ties params to search_flights output, and 'names must match passport' clarifies the given_name/family_name fields while 'email receives the e-ticket' explains contact_email/passenger email purpose. This elevates it above baseline, though the description does not systematically walk through each parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear verb+resource: 'Book a flight from a search result.' The FLOW line positions it as the final step after search_flights, distinguishing it from related tools like book_hotel and unlock_flight_offer. The purpose is unambiguous and differentiated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The FLOW explicitly states the sequence authenticate -> search_flights -> book_flight, giving clear when-to-use context. The RESULT section provides a critical 'when not to' instruction: if booked:false, do not retry but pass the booking_url to the user. This is explicit behavioral guidance beyond simple prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
book_hotelAInspect
Book one hotel rate. Charges 10% of the price to the card on file immediately as a NON-REFUNDABLE reservation fee; the balance is paid directly to the supplier through the pay link we return, by balance_due_by (the supplier's own auto-cancellation date).
Returns a booking_job_id, NOT the booking — a booking takes minutes. Poll get_hotel_booking until status is succeeded or failed.
The fee is charged BEFORE the room is committed, so a declined card costs nothing: no reservation exists and nothing is charged.
Send expected_price and expected_balance back exactly as search returned them. NOT idempotent — calling twice for the same rate books the room twice and charges two fees.
| Name | Required | Description | Default |
|---|---|---|---|
| Yes | The voucher and pay link go here. A typo loses the booking. | ||
| phone | Yes | ||
| adults | No | ||
| guests | Yes | One entry per guest: {title, first_name, last_name} | |
| city_id | Yes | ||
| check_in | Yes | yyyy-MM-dd | |
| check_out | Yes | yyyy-MM-dd | |
| city_name | Yes | ||
| hotel_code | Yes | From the chosen hotel | |
| hotel_name | No | ||
| session_id | Yes | From search_hotels | |
| combination_id | No | From the chosen offer (optional) | |
| expected_price | Yes | The offer's `price`, verbatim | |
| expected_balance | Yes | The offer's `balance_to_supplier`, verbatim | |
| special_requests | No | ||
| combination_id_v2 | Yes | From the chosen offer — identifies that exact rate | |
| phone_country_code | No | Default '48' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full transparency burden and succeeds. It discloses the 10% non-refundable fee, immediate charge, pay link for balance, return of booking_job_id instead of a confirmed booking, polling requirement, declined-card behavior, and non-idempotency. This is exceptionally transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with each paragraph serving a distinct purpose: the action and fees, the return/polling behavior, the declined-card case, and the idempotency warning. Every sentence carries essential information with zero fluff, and the most critical detail (booking one rate) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the core needed information: payment flow, asynchronous processing, return value, polling, error/decline behavior, and non-idempotency. It does not elaborate on possible failure modes (e.g., rate changes, unavailability) but for a booking initiation tool, the provided context is largely sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 65%, so the description partially compensates by emphasizing that expected_price and expected_balance must be sent exactly as returned from search. Other parameters rely on their names or schema descriptions, and while most are self-explanatory, the description doesn't add additional meaning beyond a couple of key fields.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the primary action 'Book one hotel rate' and goes beyond to explain the booking flow, distinguishing it from sibling tools such as book_flight and get_hotel_booking. The resource and verb are specific, and the scope is unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear usage context: send back expected_price and expected_balance from search results, and poll get_hotel_booking for status. It explicitly warns against repeated calls with 'NOT idempotent.' However, it does not explicitly mention alternative tools by name for contrast, though the distinction is implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
cancel_hotel_bookingAInspect
Release a hotel reservation. Free until balance_due_by; after that the hotel's own ladder applies and can reach 100%. The ladder is in the booking terms, so check the cost first.
The 10% reservation fee is NOT refunded. Takes over a minute; if it times out do NOT assume it failed — re-check before retrying.
| Name | Required | Description | Default |
|---|---|---|---|
| confirmation | Yes | From the completed booking |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses key behaviors: the non-refundable 10% fee, the operation taking over a minute, and the critical timeout guidance to not assume failure. This goes well beyond basic mutation notice and offers actionable operational cautions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two well-structured sentences: the first states purpose and the cost condition, the second covers fees and timeout behavior. Every sentence provides critical information without redundancy or padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a cancellation tool with one parameter and no output schema, the description covers essential decision factors (cost, timing, non-refundable fee) and operational handling (timeout, re-check). It is sufficient for an agent to invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already fully describes the single 'confirmation' parameter as 'From the completed booking' (100% coverage). The description adds no additional meaning about the parameter itself, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Release a hotel reservation,' a specific verb+resource phrase that clearly identifies the tool's function. It distinguishes itself from siblings like book_hotel and get_hotel_booking by focusing on the cancellation/release action, leaving no ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides clear contextual timing: 'Free until balance_due_by; after that the hotel's own ladder applies and can reach 100%.' It advises checking cost first. However, it does not explicitly name alternative tools for checking terms, so it misses the bar for explicit alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_agent_profileAInspect
[Developer API only] Get agent profile, balance and usage stats. Read-only.
Requires LETSFG_API_KEY. A PFS Bearer token has no profile — it is bound to your payment method, carries no balance, and search and booking are free.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden of disclosing behavior. It states read-only, requires an API key, and explains the PFS token limitation—unexpected behavior that is valuable. It doesn't cover rate limits or error cases, but for a simple getter this is adequate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the primary purpose, followed by crucial usage caveats. Every sentence adds value with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only endpoint with no output schema, the description provides the essential context: what it returns (profile, balance, usage stats), auth requirements, and the PFS token edge case. It lacks explicit error behavior but otherwise is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, and the schema covers 100% of them trivially. The description adds meaningful auth context (LETSFG_API_KEY) that is not in the schema, which is helpful for correct invocation. Baseline 4 for zero-parameter tools is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool fetches agent profile, balance, and usage stats with a specific verb and resource. It also distinguishes from sibling tools by explicitly noting PFS bearer tokens have no profile, making the tool's scope clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit context: it is 'Developer API only', requires LETSFG_API_KEY, and clarifies when not to use it (PFS bearer token users). It doesn't name an alternative tool, but the context is sufficient for an agent to decide when to call it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_hotel_bookingAInspect
Collect the result of a booking started with book_hotel. Poll every ~20s.
status is in_progress, succeeded or failed. On success you get confirmation, reservation_fee_charged, pay_link, balance_due, balance_due_by and the full cancellation ladder. Read-only and safe to repeat.
| Name | Required | Description | Default |
|---|---|---|---|
| booking_job_id | Yes | From book_hotel |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full responsibility. It discloses that the tool is 'Read-only and safe to repeat', describes the three possible statuses ('in_progress, succeeded or failed'), and lists successful return fields. This is substantial, though it doesn't cover failure handling or rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, tightly structured. The first sentence states the core purpose and polling frequency; the second summarizes status values and success output. Every word earns its place with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter polling tool with no output schema, the description provides the essential workflow: when to use it (after book_hotel), how to poll (20s intervals), what statuses to expect, and what a successful result contains. It could mention explicit failure behavior, but the read-only, repeatable nature and sibling cancel_hotel_booking cover most gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides 100% coverage with 'booking_job_id' described as 'From book_hotel'. The description reinforces the context by referencing book_hotel, but adds no additional semantics beyond what the schema states. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Collect the result of a booking started with book_hotel.' This specifies a verb (collect), a resource (booking result), and ties it to a specific predecessor tool, distinguishing it from siblings like book_hotel and cancel_hotel_booking.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly ties usage to book_hotel ('started with book_hotel') and provides concrete polling guidance ('Poll every ~20s'). It implies the sequential workflow and repeatability, though it doesn't explicitly state when not to use it or mention alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
load_resourcesAInspect
Load the LetsFG workflow guide (3-step booking flow, pricing, passenger rules, error handling). Call this ONCE at the start of a conversation to understand how to use the flight tools correctly. Clients that support MCP resources get this automatically — this tool is for clients that do not.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It states the tool loads a guide (read behavior) but does not disclose the return format (e.g., plain text, structured JSON) or any potential side effects. For a simple loader, this is adequate but lacks some detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise with two sentences. The key action and context are front-loaded, and every sentence adds value without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a documentation-loading tool with no parameters, output schema, or complex behavior, the description is mostly complete. It could mention that the guide is returned in the response, but the purpose and usage are well covered.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so per guidelines the baseline score is 4. The schema coverage is trivially 100%, and the description adds no parameter information as none are needed.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states it loads the LetsFG workflow guide, specifying the content areas (booking flow, pricing, rules, error handling). It clearly identifies the verb-resource pair and distinguishes from sibling tools like book_flight or search_flights.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance: call once at conversation start, and clarifies that clients with MCP resource support get this automatically, so the tool is for those without. This offers clear when-to-use and when-not-to-use context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_hotel_cityAInspect
Convert a place name to the supplier city id that search_hotels needs. Always call this first if you only have a city name. Read-only and safe to repeat.
Use Id from the first result as city_id and Name as city_name.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | Place name (e.g., 'Warsaw', 'Paris') |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden and discloses that the operation is read-only and safe to repeat. It also implies multiple results by referencing 'the first result,' which adds useful behavioral context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, front-loaded with the main purpose and includes actionable instructions on using the result, with no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter tool with no output schema, the description is thorough: it states what the tool does, when to call it, that it is safe, and how to interpret the response. This makes it complete for agent use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes the 'text' parameter as a place name with 100% coverage, so the description adds little new information. It reiterates 'place name' and 'city name' without adding format or additional constraints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool converts a place name to the supplier city id needed by search_hotels, using a specific verb and resource. It also distinguishes from siblings by positioning it as a prerequisite for search_hotels.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says 'Always call this first if you only have a city name,' providing a clear when-to-use condition. However, it does not mention alternatives or when not to use it, such as when the city ID is already known.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_locationAInspect
Convert a city/airport name to IATA codes. Always call before search_flights if you only have a city name. Read-only, safe to call multiple times.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | City or airport name (e.g., 'London', 'Berlin') |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full burden and explicitly states 'Read-only, safe to call multiple times', clearly indicating no side effects and idempotency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, no redundant words. The purpose is stated first, followed by usage guidance. It is concise and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one parameter, no output schema), the description fully covers its purpose and usage context. It also explains its relationship to sibling tools, making it complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with a clear description for the query parameter. The description adds context by specifying the output (IATA codes) and reiterating the parameter's purpose, adding value beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool converts a city/airport name to IATA codes, using a specific verb ('Convert') and resource. It also distinguishes itself from sibling tools by explicitly recommending it as a prerequisite for search_flights.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Always call before search_flights if you only have a city name', providing clear when-to-use guidance. It also notes it is read-only and safe to call multiple times, further guiding usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_flightsAInspect
Search hundreds of airlines for live flight prices — completely FREE, unlimited, read-only.
Returns structured offers with prices, airlines, times, durations, and stopovers. Covers airlines across all continents including low-cost carriers.
Search is async (60-90s): this tool handles the polling automatically.
Requires LETSFG_BEARER_TOKEN or LETSFG_API_KEY. See letsfg://guide resource for the full authenticate->search->book workflow.
| Name | Required | Description | Default |
|---|---|---|---|
| adults | No | Number of adults (default: 1) | |
| origin | Yes | IATA code of departure (e.g., 'LON', 'JFK'). Use resolve_location if you only have a name. | |
| children | No | Number of children (2-11) | |
| currency | No | Currency code (EUR, USD, GBP) | EUR |
| date_from | Yes | Departure date YYYY-MM-DD | |
| cabin_class | No | M=economy, W=premium, C=business, F=first | |
| destination | Yes | IATA code of arrival (e.g., 'BCN', 'LAX') | |
| max_results | No | Max offers to return | |
| return_from | No | Return date YYYY-MM-DD (omit for one-way) | |
| departure_time_to | No | Latest departure time HH:MM (e.g., '14:00') | |
| departure_time_from | No | Earliest departure time HH:MM (e.g., '06:00') |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully discloses critical behavioral traits: 'completely FREE, unlimited, read-only' covers cost, rate, and side effects; 'async (60-90s)' reveals latency and automatic polling; 'Requires LETSFG_BEARER_TOKEN or LETSFG_API_KEY' exposes authentication needs. This goes far beyond what annotations would provide and is highly transparent for an agent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is only four sentences, with the core purpose in the first sentence and no fluff. Each subsequent sentence adds distinct value: output contents, async behavior, and authentication/workflow. The structure is front-loaded and efficient, with every sentence earning its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of 11 parameters and no output schema, the description provides a solid overview: it states the tool is a read-only flight search, returns structured offers with key fields, is async with automatic polling, and requires authentication. It doesn't detail all possible filters or error conditions, but it points to a guide resource for the full workflow, making it sufficiently complete for most uses.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The main description does not add parameter-level detail beyond mentioning that the output includes prices, airlines, times, durations, and stopovers. It doesn't clarify any parameter semantics that the schema doesn't already cover, so the description's contribution is minimal.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Search hundreds of airlines for live flight prices,' which is a specific verb+resource combination that clearly states the tool's function. It also explicitly adds 'read-only' to distinguish itself from booking/unlocking siblings like book_flight and unlock_flight_offer, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for when to use this tool by stating it is part of an 'authenticate->search->book' workflow and by emphasizing its read-only nature. It doesn't explicitly mention alternatives, but the workflow and 'read-only' hint at its role as the search step. It also notes required authentication, which is a prerequisite for use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_hotelsAInspect
Search real, bookable hotel inventory. Requires a payment method on file — the SAME card that authorises flight booking. That applies to search too, not just booking, because a search opens a real session at the supplier.
Only free-cancellation, pay-later rates are returned, so everything you see can actually be booked on these terms. The result set is smaller than a metasearch and that is deliberate.
price is what the guest pays. Keep session_id and the chosen offer's combination_id_v2 — together they identify that exact rate, and book_hotel needs both. Takes up to a few minutes.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max hotels to return (default 40) | |
| adults | No | Adult guests (default 2) | |
| city_id | Yes | From resolve_hotel_city (`Id`) | |
| check_in | Yes | yyyy-MM-dd | |
| children | No | Child guests (default 0) | |
| check_out | Yes | yyyy-MM-dd | |
| city_name | Yes | From resolve_hotel_city (`Name`) | |
| child_ages | No | Age of each child; the supplier needs these to price | |
| nationality | No | Two-letter guest nationality. Rates and taxes genuinely differ by it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly: it discloses side effects (opening a real session), prerequisites (payment method), rate filters (free-cancellation, pay-later), result-set size, what the price represents, and the necessity of retaining identifiers for booking. This goes well beyond the schema and gives the agent essential operational knowledge.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured, with each sentence adding distinct information: purpose first, then prerequisites, then behavioral details and linkage to the booking step. No redundancy or filler, and the length is appropriate for the complexity of the tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the absence of annotations and an output schema, the description effectively covers prerequisites, side effects, performance expectations, and how the result connects to book_hotel. It clearly sets expectations for the agent, including the need to preserve identifiers and the fact that the search may take minutes. This is complete for the tool's purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides descriptions for all nine parameters, achieving 100% coverage. The description does not add meaning to the parameters themselves; it mentions output fields like price and session_id, which are outside the input schema. Therefore, a baseline 3 is appropriate, as the schema does the heavy lifting and the description offers no additional parameter-level insight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Search real, bookable hotel inventory,' which uses a specific verb and resource. It also clarifies the scoping to free-cancellation, pay-later rates and explicitly notes the result set is smaller than a metasearch, distinguishing it from broader hotel search tools and the sibling search_flights.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly states the prerequisite of a payment method on file and that this applies to search because a session is opened at the supplier. It also advises preserving session_id and combination_id_v2 for the subsequent book_hotel call and warns about the multi-minute duration, giving the agent explicit context for when this tool should be used.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
setup_paymentAInspect
[Developer API only — you almost certainly want authenticate instead] Attaches a card to a PAID prepaid Developer API account. Refuses to run unless LETSFG_API_KEY is set, because agents kept calling this and creating billing accounts they did not need.
| Name | Required | Description | Default |
|---|---|---|---|
| token | No | Payment token (e.g., 'tok_visa' for testing) | |
| payment_method_id | No | Payment method ID (pm_xxx) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden of behavioral disclosure. It reveals the potential side effect of creating unnecessary billing accounts, the refusal behavior without LETSFG_API_KEY, and the paid-account implication. It stops short of describing success/failure responses or idempotency, but for a simple payment setup this is substantive disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single compact sentence with a bracketed warning upfront. It packs purpose, target audience, prerequisite, refusal behavior, and rationale without any filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers purpose, audience, and prerequisite with strong warning context. However, the schema lists both parameters as optional and the description doesn't clarify whether the agent must provide at least one of them, which is a meaningful gap for correct invocation. Still, given the simple scope and explicit routing to `authenticate`, this is a minimally viable description with clear gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema descriptions already cover both `token` and `payment_method_id` (100% coverage), so the baseline is 3. The description adds no extra meaning about when to use one parameter over the other or whether at least one is required, which is a missed opportunity but not a penalty below baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('attaches') and names the exact resource ('card to a PAID prepaid Developer API account'), making the tool's function immediately clear. It also distinguishes itself from sibling authenticate by explicitly warning 'you almost certainly want `authenticate` instead'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states this is 'Developer API only' and directs users to `authenticate` as the usual alternative. It also discloses a hard prerequisite (LETSFG_API_KEY must be set) and explains why the tool refuses to run, which gives clear when-to-use vs when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unlock_flight_offerAInspect
[Developer API only] Confirm live price with the airline and reserve the offer for 30 minutes.
NOT part of the agent flow and NOT needed before book_flight. It requires LETSFG_API_KEY (the paid, prepaid Developer API) and refuses to run on a Bearer token, because the PFS unlock endpoint does not exist — calling it that way used to 404.
If you authenticated with letsfg auth, go straight from search_flights to book_flight.
Cost when used with a Developer API key: 1% of ticket price (min $3). Not idempotent.
| Name | Required | Description | Default |
|---|---|---|---|
| offer_id | Yes | Offer ID from search results (off_xxx) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and does so thoroughly. It discloses auth requirements ('requires LETSFG_API_KEY', 'refuses to run on a Bearer token'), potential failure mode ('calling it that way used to 404'), cost ('1% of ticket price (min $3)'), and idempotency ('Not idempotent'). This significantly exceeds basic expectations for a mutation tool without annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description front-loads the purpose and then provides necessary details in a structured way. While it is longer than the TDQS 4.3 example, every sentence adds value (auth, cost, idempotency, alternatives). The historical note about the 404 is slightly extraneous but not wasteful, so it remains appropriately concise.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has one simple parameter, no output schema, and no annotations. Despite this, the description covers purpose, usage alternatives, auth constraints, cost, and idempotency. It is fully self-contained and gives an agent everything needed to decide whether and how to invoke the tool. No gaps are evident.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage for the single parameter offer_id is 100%, and the schema already provides a clear description ('Offer ID from search results (off_xxx)'). The tool description does not add any additional meaning beyond referring to the offer. Per the rubric, high schema coverage yields a baseline of 3, and the description does not elevate it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'Confirm live price with the airline and reserve the offer for 30 minutes.' It uses specific verbs and identifies the resource (flight offer), and explicitly distinguishes itself from book_flight by stating 'NOT part of the agent flow and NOT needed before book_flight.' This fully clarifies what the tool does and how it differs from siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-not-to-use guidance: 'NOT part of the agent flow' and 'If you authenticated with `letsfg auth`, go straight from search_flights to book_flight.' It also specifies the required auth context (LETSFG_API_KEY vs Bearer token) and provides an alternative (book_flight directly). This is exemplary usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
11 tool updates
v2026.5.88- Added
authenticate - Changed
book_flight3 fields changed- changed
Input schema / properties / offer_id / descriptionPrevious value: -"Unlocked offer ID (off_xxx)"New value: +"Offer ID from search_flights" - added
Input schema / properties / search_idAdded value: +{ + "description": "search_id from search_flights", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "offer_id", - "passengers", - "contact_email" -]New value: +[ + "search_id", + "offer_id", + "passengers", + "contact_email" +]
- Added
book_hotel - Added
cancel_hotel_booking - Added
get_hotel_booking - Removed
link_github - Added
resolve_hotel_city - Changed
search_flights4 fields changed- added
Input schema / properties / departure_time_fromAdded value: +{ + "description": "Earliest departure time HH:MM (e.g., '06:00')", + "type": "string" +} - added
Input schema / properties / departure_time_toAdded value: +{ + "description": "Latest departure time HH:MM (e.g., '14:00')", + "type": "string" +} - removed
Input schema / properties / max_browsersRemoved value: -{ - "description": "Max concurrent browser processes (1-32). Default: auto-detect from system RAM.", - "type": "integer" -} - removed
Input schema / properties / modeRemoved value: -{ - "description": "Omit for full search (200+ connectors). 'fast' = ~25 connectors, 20-40s.", - "enum": [ - "fast" - ], - "type": "string" -}
- Added
search_hotels - Removed
start_checkout - Removed
system_info
10 tool updates
v2026.5.76- Added
book_flight - Added
get_agent_profile - Added
link_github - Added
load_resources - Added
resolve_location - Added
search_flights - Added
setup_payment - Added
start_checkout - Added
system_info - Added
unlock_flight_offer
TDQS
Scored across 13 tools
Each tool has a distinct purpose: flight search/book, hotel search/book/status/cancel, location resolution, and authentication. The developer-only tools are clearly labeled and separated from the agent flow, preventing confusion.
All tool names follow a consistent verb_noun pattern in snake_case (e.g., search_flights, book_hotel, get_hotel_booking). Even the developer-only tools like setup_payment and get_agent_profile adhere to the convention.
13 tools cover the full workflow for flight and hotel booking, including authentication and profile management. The count is well-scoped and each tool earns its place without redundancy.
Hotel lifecycle is complete with search, book, poll, and cancel. Flight lacks a get/cancel for bookings, but book_flight returns an immediate result, so the gap is minor. Authentication is well covered.
Maintenance
Related MCP Connectors
Personal AI travel agent. Points optimization, live flight/hotel/award search, trip planning.
Travel & commerce intelligence for AI agents: search, book & price-track hotels, events, retail.
Live flight prices and working booking links for AI agents and travel apps.
AI travel agent — book flights, hotels, activities, and events worldwide via autonomad.ai.
Related MCP Servers
- -
- AlicenseAqualityFmaintenanceMCP server searching flights with granular filtering, sorting options, and purchase integration.415PythonGPL 3.0

autonomad-travelofficial
AlicenseAqualityDmaintenanceAI travel agent over MCP — live flights, hotels, activities, and events worldwide, then completes the booking on autonomad.ai.8249 npm2MIT- AlicenseAqualityFmaintenanceAI-native travel search MCP with affiliate booking links and optional x402 premium intelligence.6559 npmMIT