
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
letsfg-mcp
Advanced tools
LetsFG MCP Server — flight and hotel search and booking for Claude, Cursor, Windsurf and any MCP agent. Hundreds of airlines, plus bookable hotel rates with free cancellation and pay-later terms.
Hundreds of airlines. Real prices. One tool call. The largest open flight-search MCP server. Search is powered server-side at letsfg.co — Ryanair, EasyJet, Wizz Air, Southwest, AirAsia, Qantas, and 194 more — plus enterprise GDS/NDC providers (Amadeus, Duffel, Sabre) through the LetsFG backend.
Your agent doesn't need to build a flight integration. Just add this MCP server.
LetsFG is not a scraper wrapper. It's a production-grade agent-to-airline connector layer:
book_flight holds the fare on that card, a LetsFG booking agent buys the ticket, and the hold is captured only once a real airline PNR existsLON and it auto-searches LHR, LGW, STN, LTN, SEN, LCY. Works for 25+ major cities worldwideWhy agents should use this instead of building their own:
Recommended — the hosted MCP, no install:
https://letsfg.co/developers/api/mcp
Add it as a remote MCP server in Claude, ChatGPT, Cursor or Windsurf (or
claude mcp add --transport http letsfg https://letsfg.co/developers/api/mcp
in Claude Code) and approve the connection. The consent step opens
letsfg.co/connect, where you add a card (any
card, or Revolut Pay / Google Pay) in a 0.00 Revolut setup. Nothing is
charged, no Revolut account is needed, and the card details go to Revolut,
never to LetsFG. The token is card-backed: it searches and it books, and it
is carried on every tool call for you.
This package — the stdio server, runs on your machine:
npx letsfg-mcp
It needs that same card-backed token in LETSFG_BEARER_TOKEN. To mint one
from the command line, run letsfg auth: it registers itself as an OAuth
client, opens letsfg.co/connect for a person to approve, and writes the token to
~/.letsfg/config.json (--no-browser prints the URL instead). This server's
own authenticate tool returns the current instructions rather than minting a
token, since a person has to approve in a browser either way.
Retired 2026-09-02: the Stripe card setup and every token it issued (401
TOKEN_REVOKED). Reconnect at letsfg.co/connect.
The hosted server is the simplest option everywhere: it does the connect flow for you and needs no token in a config file.
Add a custom connector with the URL https://letsfg.co/developers/api/mcp
and approve it. The consent step takes you through letsfg.co/connect.
claude mcp add --transport http letsfg https://letsfg.co/developers/api/mcp
Add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"letsfg": { "url": "https://letsfg.co/developers/api/mcp" }
}
}
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"letsfg": { "serverUrl": "https://letsfg.co/developers/api/mcp" }
}
}
npx letsfg-mcp)Same shape in any of the files above, with the card-backed token from the
connect flow in the environment (a paid Developer API key in
LETSFG_API_KEY also works, on that separate product):
{
"mcpServers": {
"letsfg": {
"command": "npx",
"args": ["-y", "letsfg-mcp"],
"env": {
"LETSFG_BEARER_TOKEN": "eyJ..."
}
}
}
}
Add to ~/.continue/config.yaml:
mcpServers:
- name: letsfg
command: npx
args: ["-y", "letsfg-mcp"]
env:
LETSFG_BEARER_TOKEN: eyJ...
Connect via remote MCP (no install, does the card connect for you):
https://letsfg.co/developers/api/mcp
Or run the stdio server with a token in LETSFG_BEARER_TOKEN:
npx letsfg-mcp
npx ENOENT FixIf you get spawn npx ENOENT on Windows, use the full path to npx:
{
"mcpServers": {
"letsfg": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": ["-y", "letsfg-mcp"],
"env": {
"LETSFG_BEARER_TOKEN": "eyJ..."
}
}
}
}
Or use node directly:
{
"mcpServers": {
"letsfg": {
"command": "node",
"args": ["C:\\Users\\YOU\\AppData\\Roaming\\npm\\node_modules\\letsfg-mcp\\dist\\index.js"],
"env": {
"LETSFG_BEARER_TOKEN": "eyJ..."
}
}
}
}
To avoid unexpected updates:
{
"command": "npx",
"args": ["-y", "letsfg-mcp@1.0.0"]
}
| Tool | Description | Cost | Side Effects |
|---|---|---|---|
search_flights | Search hundreds of airlines via server-side engine | FREE | None (read-only) |
resolve_hotel_city | Place name -> supplier city id | FREE | None (read-only) |
search_hotels | Search bookable hotel rates (refundable and non-refundable) | Free up to 1,000/booking, then $5/1,000 — needs a connected payment method | Opens a supplier session |
book_hotel | Start a hotel booking (async, returns a job id) | The price: held on the connected method, captured once the hotel confirms, released if it fails | Places a hold, books and pays the room |
get_hotel_booking | Collect the booking result (succeeded / failed / attention) | FREE | None (read-only) |
cancel_hotel_booking | Cancel this account's booking and refund the guest | Free on a refundable rate before free_cancellation_until; a cancellation that would cost money is refused | Cancels the booking |
resolve_location | City name → IATA code | FREE | None (read-only) |
book_flight | Start a real booking: fare held on the connected card, a LetsFG agent buys the ticket, captured on a real PNR | Ticket price (LetsFG's markup is inside the price) | Places a hold, creates the booking |
get_flight_booking | Poll a booking started by book_flight until completed / failed / needs_attention | FREE | None (read-only) |
unlock_flight_offer | RETIRED 2026-09-08 — the tool refuses locally and the route answers 410 Gone | — | Call book_flight directly |
connect_payment | Developer API only — mint a link to connect a payment method to a paid prepaid account. Not how agents connect. Replaced setup_payment on 2026-09-08 with the Stripe lane | FREE | Returns connect_url |
get_agent_profile | Usage stats & payment status | FREE | None (read-only) |
PFS (card-backed token from letsfg.co/connect):
connect (once) → search_flights → book_flight → get_flight_booking (poll)
(0.00 setup) (free) (hold + agent) (PNR in 4-11 min)
search_flights("LON", "BCN", "2026-06-15") — server-side search returns offers from hundreds of airlines in 8–10 s to first results; collect late arrivals with get_flight_resultsbook_flight(search_id, offer_id, passengers, contact_email) — exactly what the website checkout does: the fare plus LetsFG's markup is held on the connected card (not taken), a LetsFG booking agent buys the ticket from the seller, and the hold is captured only once a real airline PNR exists. Returns a booking_ref within seconds. One traveller per call, with the details an airline checkout asks for (name, date of birth, gender, nationality, email, phone with its country, residence address; passport optional). A missing detail returns missing_fields and charges nothing.get_flight_booking(booking_ref) every 20–30 s — booking_in_progress → completed (PNR, captured amount) | failed (hold released, nothing charged) | needs_attention (a human at LetsFG is checking; do not book again). A booking legitimately takes 4–11 minutes.No unlock step, no booking-link fallback, no separate LetsFG fee. Never call
book_flight twice for the same trip while one is in progress — that would
place a second hold.
The stdio server books through the same
/api/agent-bookhold + poll flow as the hosted MCP:book_flightreturns abooking_ref, thenget_flight_bookingreports the outcome. It takes one passenger per call and refuses more rather than silently booking only the first. (Until 2026-09-02 this package had noget_flight_bookingat all, so a stdio agent could start a booking and never learn whether it landed — that is fixed.)
Developer API (look-to-book search, real booking, hotels):
The Developer API books flights itself: POST /flights/book holds
the fare on a connected Revolut method and a LetsFG booking agent buys the ticket, exactly like
this package does. Every offer carries a booking_url that points at a letsfg.co page for that
offer — never a seller deep link. Search is 200 free after every booking, then $0.01. No booking
fee, no transaction fee. Use this path for volume, account-level billing, or hotels.
The agent has native tools — no API docs needed, no URL building, no token-burning browser automation.
search_flights and search_hotels accept an optional response_mode parameter:
| Mode | Default | What's returned | Best for |
|---|---|---|---|
"summary" | ✅ | Price, airlines, route, departure, stops | Chat, quick comparisons |
"full" | Everything: segments, durations, conditions, bags, booking URLs | Deep analysis, programmatic use |
Summary mode saves tokens by stripping per-segment details, baggage policies, and booking conditions. Ask for response_mode: "full" on search_flights or get_flight_results when you need them.
// summary response (search_flights)
{
"total_offers": 42,
"offers": [
{ "id": "off_abc", "price": "€29", "airlines": ["FR"], "route": "STN→BCN", "departure": "06:15", "stops": 0 }
],
"hint": "More offers are still landing — call get_flight_results before recommending."
}
Offers may carry starlink: confirmed_all / confirmed_some mean the carrier
has fully fitted that aircraft type; likely_all / likely_some mean the
rollout on that type is underway but incomplete. Segments carry confirmed or
likely.
Only confirmed_* is safe to state as fact — likely_* is a signal, not a
promise. Anything ending _some has at least one leg without it. An absent
field means no information, not an absence of Wi-Fi.
Full semantics: docs/api-search.md.
Most agents do not need this: the card-backed token from letsfg.co/connect already searches and books. An API key belongs to the separate, paid, prepaid-balance Developer API. If that is what you want, register at letsfg.co/developers or via CLI:
pip install letsfg
letsfg register --name my-agent --email you@example.com
Or directly via the API:
curl -X POST https://letsfg.co/developers/api/v1/agents/register \
-H "Content-Type: application/json" \
-d '{"agent_name": "my-agent", "email": "agent@example.com"}'
┌──────────────────────────────────────────────────────────────┐
│ MCP Client (Claude Desktop / Cursor / Windsurf / etc.) │
│ ↕ stdio (JSON-RPC) │
├──────────────────────────────────────────────────────────────┤
│ letsfg-mcp (this package, runs on YOUR machine) │
│ │ │
│ └─→ HTTPS to letsfg.co (all search + booking) │
│ search, book (hold → agent → PNR), GDS/NDC feeds │
└──────────────────────────────────────────────────────────────┘
All search and booking run server-side at letsfg.co. No local browsers or scrapers are involved. Connect a card once at letsfg.co/connect (0.00 Revolut setup, nothing charged) and the token is card-backed for search and booking.
| Operation | Where data flows | What is sent |
|---|---|---|
search_flights | Your machine → letsfg.co → airlines + GDS providers | Route, date, passenger count |
resolve_location | Your machine → letsfg.co | City/airport name |
book_flight | Your machine → letsfg.co → LetsFG booking agent → seller | Passenger name, DOB, nationality, email, phone, address; the hold goes to Revolut |
get_flight_booking | Your machine → letsfg.co | booking_ref |
connect_payment | Your machine → letsfg.co (Developer API only) | Nothing but your API key; the card is entered by a person on the returned page |
LETSFG_API_KEY grants access only to your agent's account. Keys are prefixed letsfg_ for easy identification and revocation.Search costs nothing, so develop against real results. book_flight places a
real hold on a real card; there is no test card on the PFS lane. The paid
Developer API has a keyless sandbox — see
docs/api-sandbox.md.
spawn npx ENOENT on WindowsWindows can't find npx in PATH. Use the full path:
"command": "C:\\Program Files\\nodejs\\npx.cmd"
Or install globally and use node directly (see Windows config above).
If a tool returns HTTP 403 … an anti-bot challenge page was returned instead of the API,
the request never reached the search engine — something in the network path in front of
letsfg.co answered it, and no token can get past that. It is seen from datacenter and
VPS IPs, which is where MCP servers normally run.
Upgrade first: 2026.5.74 and later send a declared-bot User-Agent that is not singled
out this way (Mozilla/5.0 (compatible; letsfg-mcp/1.3.1; +https://github.com/LetsFG/LetsFG)).
If you pinned an older version, that is the likeliest cause.
If it still happens, override the User-Agent yourself:
"env": {
"LETSFG_BEARER_TOKEN": "eyJ...",
"LETSFG_USER_AGENT": "Mozilla/5.0 (compatible; letsfg-mcp)"
}
Please also open an issue with your host and egress IP — the durable fix belongs on our side, not in your config.
resolve_location firstLETSFG_BEARER_TOKEN for the stdio server)Connect LetsFG as an MCP server at https://letsfg.co/developers/api/mcp and approve it; the consent step opens letsfg.co/connect, where you add a card in a 0.00 Revolut setup. Nothing is charged and there is no authorization hold. The token you get is card-backed and works for POST /api/search and POST /api/agent-book. Tokens from the old letsfg auth (Stripe) flow were revoked on 2026-09-02 — reconnect.
Book. book_flight does what the website checkout does: the fare is held on the connected card, a LetsFG booking agent buys the ticket, and the hold is captured only once a real airline PNR exists — for every offer, not just one supplier. Poll get_flight_booking until completed; a failed booking releases the hold and charges nothing.
Yes. MIT license. The SDK, MCP server, and ranking engine are fully open source.
Ensure Node.js 18+ is installed. The server communicates via stdio (stdin/stdout JSON-RPC) — it doesn't open a port or print a "ready" message. MCP clients handle the lifecycle automatically.
| Region | 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, IndiGo, SpiceJet, Akasa Air, Air India, Air India Express, VietJet, Cebu Pacific, Scoot, Jetstar, Peach, Spring Airlines, Lucky Air, 9 Air, Nok Air, Batik Air, Jeju Air, T'way Air, ZIPAIR, 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 | American Airlines, Delta, United, Southwest, JetBlue, Alaska Airlines, Hawaiian Airlines, Sun Country, Frontier, Volaris, VivaAerobus, Allegiant, Avelo, Breeze, Flair, GOL, Azul, JetSmart, Flybondi, Porter, WestJet, LATAM, Copa, Avianca, Air Canada, Arajet, Wingo, Sky Airline |
| Aggregator | Kiwi.com (virtual interlining + LCC fallback) |
npm install letsfg — npmpip install letsfg — PyPI⭐ Star the repo — we appreciate the support.
MIT
Your agent can now book hotels, not just flights. Same API key, same connected payment method.
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"], offer["currency"], offer["refundable"])
# prices are in stays["currency"]: USD unless you pass currency=
booking = lfg.book_hotel_and_wait(
session_id=offer["session_id"],
hotel_code=hotel["hotel_code"],
combination_id_v2=offer["combination_id_v2"],
expected_price=offer["price"], # copy these four from the offer, verbatim
expected_cost=offer["expected_cost"],
currency=offer["currency"],
fx_rate=offer["fx_rate"],
city_id=city["Id"], city_name=city["Name"],
check_in="2026-11-10", check_out="2026-11-12",
# ONE entry per guest in the room, children included: adults first, then children in child_ages order
guests=[{"title": "Mr", "first_name": "Jan", "last_name": "Kowalski"},
{"title": "Mrs", "first_name": "Anna", "last_name": "Kowalska"}],
email="GUEST_EMAIL", phone="512345678", # the guest's real e-mail and phone
)
if booking["status"] == "succeeded":
print(booking["confirmation"], booking["total_price"], booking["currency"])
elif booking["status"] == "failed":
print("Not booked, nothing charged:", booking["error"])
else: # "attention": a person is checking it with the supplier, the hold is kept. Do not book again.
print(booking["status"], booking.get("error"))
The full price is held, and taken only once the hotel confirms. Booking holds the offer's
price on the Revolut payment method connected to your account — authorised, not charged.
LetsFG then books the room with the supplier and pays the supplier itself. The hold is captured
only after the supplier has confirmed the booking; if the booking fails for any reason (the rate
is gone, the price moved, the supplier declined, a guest detail was rejected), the hold is
released and nothing is charged.
There is no reservation fee, no deposit and no pay link, and the guest owes the hotel nothing further. (Those belonged to the process retired on 2026-09-11.)
price is the supplier's cost plus 6.4% (our margin and payment processing) for Revolut Pay or a
card issued in the EEA, or 8.3% for a card issued outside the EEA — the search response's
markup_rate says which. Nothing is added at booking. Prices are in the currency you search in:
USD unless you ask for another.
Cancelling a refundable rate before its free_cancellation_until costs nothing and refunds the
charge in full. A cancellation that would cost money is refused (409); the hotel's own ladder is in
the booking's terms.
Search is metered separately from booking, on either auth path (free PFS
Bearer token or Developer API key — both count against the same agent):
the first 1,000 search_hotels calls since your last hotel booking are
free. Past that, searches are billed in blocks of 1,000 for $5
(~$0.005/search) from your prepaid balance — refused with a 402 if the
balance can't cover the next block, never silently allowed. Book a hotel
and the count resets to zero. Resolving a city name (resolve_hotel_city)
is not metered, only the search call itself.
refundable and
free_cancellation_until say which one you are buying.book_hotel returns a booking_job_id, not a booking — the real
thing takes minutes. Poll hotel_booking(job_id) every ~20 s until status is succeeded,
failed or attention, or call book_hotel_and_wait and let the SDK do it. All three are final:
succeeded — confirmation, total_price + currency (what the guest is charged),
supplier_paid + supplier_currency, refundable, free_cancellation_until,
cancellation_ladder and terms.failed — error, written for the guest. The hold has been released; nothing was charged.attention — the outcome could not be settled automatically. The hold is kept (nothing is
charged) while a person checks with the supplier. Do not book again.expected_price (the offer's price), expected_cost,
currency and fx_rate exactly as search returned them. A mis-copied price, or a USD offer sent
without its currency, is refused with 400 price_mismatch before anything is held.400 invalid_details naming the fields.children and
child_ages; the party travels with the offer's session. guests then lists every guest — adults
first, then children in child_ages order. The hotel requires a name for every guest: fewer names
than guests is refused before anything is submitted, and the hold is released.book_hotel for the same rate and guest returns the job
already under way (duplicate: true); pass idempotency_key to make that explicit. Still, never
re-post a booking whose job is running — poll it.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 hotel = stays.hotels[0];
const offer = hotel.offers[0];
const booking = await lfg.bookHotelAndWait({
sessionId: offer.session_id, hotelCode: hotel.hotel_code,
combinationIdV2: offer.combination_id_v2,
expectedPrice: offer.price, expectedCost: offer.expected_cost,
currency: offer.currency, fxRate: offer.fx_rate,
cityId: city.Id, cityName: city.Name,
checkIn: '2026-11-10', checkOut: '2026-11-12',
// ONE entry per guest in the room, children included: adults first, then children in childAges order
guests: [{ title: 'Mr', first_name: 'Jan', last_name: 'Kowalski' },
{ title: 'Mrs', first_name: 'Anna', last_name: 'Kowalska' }],
email: 'GUEST_EMAIL', phone: '512345678',
});
console.log(booking.status, booking.confirmation, booking.total_price, booking.currency);
Five new tools, in the order you call them: resolve_hotel_city →
search_hotels → book_hotel → get_hotel_booking → cancel_hotel_booking.
FAQs
LetsFG MCP Server — flight and hotel search and booking for Claude, Cursor, Windsurf and any MCP agent. Hundreds of airlines, plus bookable hotel rates with free cancellation and pay-later terms.
The npm package letsfg-mcp receives a total of 119 weekly downloads. As such, letsfg-mcp popularity was classified as not popular.
We found that letsfg-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.