Travel Query

Search live flights from one Claude conversation: resolve places, compare real fares and their refund and change rules, and refresh the one you want before you decide. Every search asks two sources at once: Duffel, and Ignav, which adds airlines Duffel does not sell (Southwest, JetBlue, Frontier and other low-cost carriers).

Beta, test mode. Duffel's fares come from its test environment (“Duffel Airways”): not real flights or prices, and every answer says so. Ignav has no test mode, so its fares are real. Ignav fares are booked on the airline's (or an agency's) own site: Claude hands you the link. Card and passport details never pass through your conversation.

Connect

You need a Travel Query key. Ask for one and you get a single URL that is the whole of your setup:

https://killer-tools.vercel.app/mcp/travelquery/<your key>

Claude Desktop

Settings → Connectors → Add custom connector → paste the URL → Connect, then relaunch Desktop once.

Claude Code

claude mcp add --transport http travelquery https://killer-tools.vercel.app/mcp/travelquery/<your key>

Other MCP clients

Add a remote (Streamable HTTP) server with that URL. No headers, no OAuth.

What to ask

“I need to get from Boston to Lisbon the second week of March, back the following Sunday.”

Tools: find_places → search_flights (two slices) → get_offerWatch for: Your assistant should ask for exact dates and how many travellers before searching: every search is a real airline request against a daily budget.

“Cheapest nonstop London to New York on the 14th?”

Tools: find_places (London → LON) → search_flights (max_connections 0)Watch for: A city code searches every London airport. The price shown is a search price; it is not final until get_offer refreshes it.

“Raleigh to Medellín, Nov 2-16, nothing before 7am or after 10pm, no red-eyes.”

Tools: find_places → search_flights (two slices, time_window with no_overnight)Watch for: Results are itineraries: each schedule once, with its Basic / Classic / Full fares inside. A fuzzy place match ('Medilla') means confirm, not proceed.

“Is the 40-pound-more fare worth it over the basic one?”

Tools: compare_offersWatch for: The difference is usually in the conditions: refund and change rules, and their penalties. 'allowed: null' means the airline did not say, not 'no'.

Workflows

Tools

find_placesFind airports and cities

Resolve a place name to IATA codes: 'London' -> LON (city) with LHR, LGW, STN...; 'JFK' -> John F. Kennedy International. Call this before search_flights whenever the person names a place rather than a code. Each result has match_type (exact | partial | fuzzy | country) and a 0-1 confidence (fuzzy never exceeds 0.7). All fuzzy means a probable typo: the closest spellings are offered -- ASK which one. 'country' means the query named a country: ask which city. Never invent a code -- on no_data, ask. Returns an envelope: {status, mode, data, coverage, as_of, notes}. status is ok | partial | no_data | expired | not_found. mode is 'test' or 'live' -- in test mode every fare is a Duffel fixture, not a real flight or price, and you must say so. `expired`: the offer existed and can no longer be booked -- search again. `not_found`: the id is wrong -- do NOT search again for it. Always pass notes[] on to the person.

search_flightsSearch flights

Search live fares from TWO sources at once -- Duffel, and Ignav, which adds airlines Duffel does not sell (Southwest, JetBlue, Frontier and other low-cost carriers); each fare says its source, and data.suppliers says what each answered. One slice is one-way; two (out and back) is a return; up to four for multi-city (Ignav takes at most two). A round trip comes back OUTBOUND-FIRST: each outbound flight with its best 3 returns. Otherwise results are ITINERARIES: one schedule with its fares. Ranked 'best' by default -- fare plus a stated cost of time, overnights and long layovers -- because the cheapest trip is often one nobody wants. Compact by default; from_price and fares[].total are for ALL passengers together. Use time_window for time-of-day rules ('nothing before 7am, no red-eyes'; airport_buffer_minutes makes it the traveller's day, not the airport's) and max_layover_minutes to cap connections. Each call is a real request to airlines and spends one of the key's daily searches, so ask for missing dates or passenger counts rather than searching speculatively -- and use more_results, which is free, to see past the first page. Prices are NOT final: call get_offer before the person decides. Never ask for passport numbers, dates of birth or payment details in the conversation. A fare with source "ignav" is booked on the provider's own site (get_offer returns its booking_links). Returns an envelope: {status, mode, data, coverage, as_of, notes}. status is ok | partial | no_data | expired | not_found. mode is 'test' or 'live' -- in test mode every fare is a Duffel fixture, not a real flight or price, and you must say so. `expired`: the offer existed and can no longer be booked -- search again. `not_found`: the id is wrong -- do NOT search again for it. Always pass notes[] on to the person.

more_resultsMore results from a search

The next page of an earlier search_flights, with the same filters, grouping and sort. Pass search_id exactly as returned and the offset from the notes. This lists what the airlines already returned -- it does NOT spend a search. Offers that have expired since are left out and counted in the notes. Returns an envelope: {status, mode, data, coverage, as_of, notes}. status is ok | partial | no_data | expired | not_found. mode is 'test' or 'live' -- in test mode every fare is a Duffel fixture, not a real flight or price, and you must say so. `expired`: the offer existed and can no longer be booked -- search again. `not_found`: the id is wrong -- do NOT search again for it. Always pass notes[] on to the person.

get_offerRefresh one offer

Fetch one offer's CURRENT price, conditions and booking deadline. Search prices go stale within minutes; this is the number to quote when the person is deciding. For a fare with source "ignav" (offer_id ign_...) it also returns booking_links: the airline's (or an agency's) own pages where the person books it. Pass checked_bags to get the all-in price with bags, and the search_id the offer came from so all_in.alternative can name a fare on the same flights that already includes the bags -- one call answers 'what does this really cost with a bag?'. `expired` means search again; `not_found` means the id is wrong. Returns an envelope: {status, mode, data, coverage, as_of, notes}. status is ok | partial | no_data | expired | not_found. mode is 'test' or 'live' -- in test mode every fare is a Duffel fixture, not a real flight or price, and you must say so. `expired`: the offer existed and can no longer be booked -- search again. `not_found`: the id is wrong -- do NOT search again for it. Always pass notes[] on to the person.

compare_offersCompare offers side by side

Fetch 2-4 offers fresh and compare them. diff[] gives each relative to the cheapest: price delta, all-in delta (with checked_bags; ranked among offers that have one), checked bags, duration delta, stops_per_slice (one entry per leg -- [1, 1] is one stop each way, NOT two stops out), and whether it can be refunded or changed and at what penalty. Offers for DIFFERENT trips (route, dates, legs or passengers) are not ranked: data.comparable is false and the notes say why. Duplicate ids are ignored. Returns an envelope: {status, mode, data, coverage, as_of, notes}. status is ok | partial | no_data | expired | not_found. mode is 'test' or 'live' -- in test mode every fare is a Duffel fixture, not a real flight or price, and you must say so. `expired`: the offer existed and can no longer be booked -- search again. `not_found`: the id is wrong -- do NOT search again for it. Always pass notes[] on to the person.

Limits

Per key, rolling 24 hours:

Calls100 a day, 20 a minute
search_flights30 a day. Each is a live request to airlines. Paging with more_results, refreshing and comparing do not count, and an invalid search is rejected before it is counted.

A refused call says what ran out and when it resets. It never implies anything about a flight.