Skip to main content

aviasales-mcp

MCP server for flight price search via Aviasales / Travelpayouts Data API. Thirteen read-only tools that let Claude Code, Claude Desktop, Cursor or any other MCP client answer flight-price questions: fares by route and month, the cheapest day to fly, flexible dates, budget and inspiration search, plus airport/city/airline lookup.

Unlike Google-Flights-scraping MCP servers, place names do not have to be guessed into IATA codes by the model: lookup_cities and find_nearest_airports resolve them.

Example prompts

Ask in plain language. The server picks the tool.

  • How much is a flight from Moscow to Istanbul in March?
  • What is the cheapest day to fly to Bangkok in September?
  • I am flying Berlin to Lisbon on 12 May and back on the 19th. Would shifting a day either way be cheaper?
  • Where can I fly from St Petersburg for under 30 000 rubles?
  • Which airport should I fly into for Pattaya, and what does it cost from Dubai?

Common prompts

The same kind of question, with the tools a typical agent reaches for.

Prompt Tools it reaches for
"How much is a flight from Moscow to Istanbul in March?" lookup_cities → search_flights
"What's the cheapest day to fly to Bangkok in September?" get_prices_calendar
"I'm flying Berlin→Lisbon on 12 May, back on the 19th — would shifting a day either way be cheaper?" get_flexible_date_prices
"Where can I fly from St Petersburg for under 30 000 ₽?" get_city_directions, search_by_price_range
"Which airport should I fly into for Pattaya, and what does it cost from Dubai?" find_nearest_airports → search_flights
"Evening departures only, two adults and a child, business class." search_flights with depart_after, adults, children, trip_class

Install

Paste this into a terminal. It is the same docker command every client block below runs; the process speaks MCP on stdin and waits there.

AVIASALES_API_TOKEN=your-token-here AVIASALES_MARKET=ru \
  docker run -i --rm \
  -e AVIASALES_API_TOKEN \
  -e AVIASALES_MARKET \
  ghcr.io/stufently/aviasales-mcp:latest

You need Docker, and a free Travelpayouts API token from https://www.travelpayouts.com/programs/100/tools/api. A wrong token still lets the process start; it surfaces as an error on the first search. AVIASALES_MARKET is optional — see Configuration. Drop that variable and its -e pair if you do not need it.

:latest follows releases; pin a version tag (:0.5.2) if you would rather upgrade by hand. The package is not on PyPI yet: the release workflow is ready, and the first upload waits for the maintainer to register the project on PyPI. Until then uvx aviasales-mcp finds nothing and the command above is the install path.

MCP client configs

Every block runs the same docker command as the terminal snippet above. -e AVIASALES_API_TOKEN with no value hands the container the variable the client sets in env, so the token is written in exactly one place.

Keep the token out of anything you commit. A project-level .mcp.json, .cursor/mcp.json or .zed/settings.json is a normal thing to check into git, and a token pasted there goes with it. Prefer the user-level config file, or .gitignore the project one.

Claude Code

Project file: .mcp.json in the project root.

{
  "mcpServers": {
    "aviasales": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "AVIASALES_API_TOKEN",
        "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"
      ],
      "env": {
        "AVIASALES_API_TOKEN": "your-token-here",
        "AVIASALES_MARKET": "ru"
      }
    }
  }
}

Or from the terminal:

claude mcp add --scope user aviasales \
  -e AVIASALES_API_TOKEN=your-token-here -e AVIASALES_MARKET=ru -- \
  docker run -i --rm -e AVIASALES_API_TOKEN -e AVIASALES_MARKET \
  ghcr.io/stufently/aviasales-mcp:latest

--scope user makes it available in every project and keeps the token in ~/.claude.json, out of the repository. Check with claude mcp list.

Claude Desktop

File: claude_desktop_config.json — macOS ~/Library/Application Support/Claude/claude_desktop_config.json, Windows %APPDATA%\Claude\claude_desktop_config.json, Linux ~/.config/Claude/claude_desktop_config.json.

{
  "mcpServers": {
    "aviasales": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "AVIASALES_API_TOKEN",
        "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"
      ],
      "env": {
        "AVIASALES_API_TOKEN": "your-token-here",
        "AVIASALES_MARKET": "ru"
      }
    }
  }
}

One click, without editing JSON: download the .mcpb from https://github.com/stufently/aviasales-mcp/releases/latest and open it. Claude Desktop installs the extension and asks for the Travelpayouts API token.

The same release includes checksums.txt, the SHA256 of every file attached to it: the .mcpb, and from the first PyPI release on also the sdist and the wheel, byte for byte the files uploaded to PyPI. Download it into the same directory as the bundle and check it before opening the bundle:

sha256sum -c --ignore-missing checksums.txt   # run next to the downloaded files

On macOS, shasum -a 256 -c checks the same file.

Claude Desktop starts with a trimmed PATH and may not find docker by name: if the server fails to start, replace "docker" with its absolute path (which docker).

Cursor

File: ~/.cursor/mcp.json (every project) or .cursor/mcp.json (this project).

{
  "mcpServers": {
    "aviasales": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "AVIASALES_API_TOKEN",
        "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"
      ],
      "env": {
        "AVIASALES_API_TOKEN": "your-token-here",
        "AVIASALES_MARKET": "ru"
      }
    }
  }
}

Windsurf

File: mcp_config.json — macOS and Linux ~/.config/devin/mcp_config.json (or $XDG_CONFIG_HOME/devin/mcp_config.json), Windows %APPDATA%\devin\mcp_config.json. Older builds read ~/.codeium/windsurf/mcp_config.json.

{
  "mcpServers": {
    "aviasales": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "AVIASALES_API_TOKEN",
        "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"
      ],
      "env": {
        "AVIASALES_API_TOKEN": "your-token-here",
        "AVIASALES_MARKET": "ru"
      }
    }
  }
}

Zed

File: settings.json at ~/.config/zed/settings.json (or zed: open settings).

{
  "context_servers": {
    "aviasales": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "AVIASALES_API_TOKEN",
        "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"
      ],
      "env": {
        "AVIASALES_API_TOKEN": "your-token-here",
        "AVIASALES_MARKET": "ru"
      }
    }
  }
}

Codex

codex mcp add aviasales --env AVIASALES_API_TOKEN=your-token-here -- \
  docker run -i --rm -e AVIASALES_API_TOKEN ghcr.io/stufently/aviasales-mcp:latest

Or by hand in ~/.codex/config.toml:

[mcp_servers.aviasales]
command = "docker"
args = ["run", "-i", "--rm", "-e", "AVIASALES_API_TOKEN", "-e", "AVIASALES_MARKET",
        "ghcr.io/stufently/aviasales-mcp:latest"]

[mcp_servers.aviasales.env]
AVIASALES_API_TOKEN = "your-token-here"
AVIASALES_MARKET = "ru"

Any other stdio client

Command docker, arguments run -i --rm -e AVIASALES_API_TOKEN ghcr.io/stufently/aviasales-mcp:latest, and AVIASALES_API_TOKEN in the environment the client gives the process. The server is also listed in the official MCP registry as io.github.stufently/aviasales-mcp, which clients with a registry browser can install from directly.

Verified clients and OS

Only what was actually run is marked verified. Everything else is a config written from the client's documentation and has not been tried.

Client or path OS Status How
Test suite, Python 3.12 / 3.13 / 3.14 Linux (GitHub ubuntu-latest) Verified on every PR and push to main ci.yml; HTTP is mocked
Docker runtime image build Linux (GitHub ubuntu-latest) Verified on every PR and push to main ci.yml
Plain MCP over stdio (initialize, tools/list) with the Docker image Linux x86_64 Verified 2026-10-12 13 tools listed, every one readOnlyHint: true, destructiveHint: false
Claude Code 2.1.296, Docker command from this README Linux x86_64 Verified 2026-10-12: connects and lists all 13 tools A placeholder token; no tool call reached the API in this check
uv run launch used by the .mcpb Linux, ghcr.io/astral-sh/uv image Verified 2026-10-10: starts and answers tools/list Without Claude Desktop
Travelpayouts Data API with a real token Linux Verified 2026-07-29 (live probe of the Data API endpoints) See CHANGELOG
Claude Desktop (.mcpb or JSON config) macOS, Windows, Linux Not verified No Claude Desktop on the test host
Cursor, Windsurf, Zed, Codex any Not verified Configs follow each client's docs as of 2026-10-10
Any client on macOS or Windows macOS, Windows Not verified
pip install / uvx from PyPI any Not verified Not on PyPI yet

Tools

All thirteen tools only read: they search cached fares and reference data. None books, buys, writes or changes anything, and none costs money to call beyond the Travelpayouts rate limit, so there is no separate read-only mode to turn on.

Flight prices

Tool Description
search_flights Prices between two cities on a date or across a month (v3/prices_for_dates)
get_prices_calendar Prices grouped by day or month — the cheapest day to fly
get_flexible_date_prices Prices for the days around your dates — "would shifting a day be cheaper?"
get_latest_prices Most recently found fares, optionally filtered by route
get_popular_directions Where travellers reach a destination from
get_city_directions Cheapest destinations reachable from a city — inspiration search
get_alternative_directions Prices for nearby airports/cities
search_by_price_range Flights inside a budget; omit the destination to search anywhere (no date filter — the endpoint ignores one)

search_flights, get_prices_calendar, get_flexible_date_prices and get_latest_prices accept adults (1–9), children (0–8), infants (0–8) and trip_class (economy/comfort/business/first).

The Data API serves a cache of recent searches and takes no passenger parameters, so the party is encoded into each ticket's booking_link instead — the link opens Aviasales with the full party and class pre-filled and shows the real total. The prices themselves are always per adult in economy, which is what the price_note field on every price response spells out for the model.

search_flights also takes depart_after / depart_before (HH:MM, 24-hour) to keep only departures in a time window; set depart_after later than depart_before for a window that wraps midnight (red-eyes).

Tickets carry duration_total (door-to-door minutes), duration_to / duration_back (flight time per direction) and layover_minutes (combined ground time between connections). Every price response also carries a price_summary (min/median/max) so the model can tell a good fare from a bad one without a second search.

Reference data

Tool Description
lookup_airlines Airlines by name or IATA code
lookup_airports Airports by name, IATA code, or city code
lookup_cities Cities by name or IATA code — turn a city name into a code
lookup_countries Countries by name or code
find_nearest_airports Airports closest to a place or to lat/lon, by distance

The lookup_* tools take search, limit (default 50, max 500) and locale, and return {status, total, returned, truncated, data}. Always pass search: the underlying datasets are ~10k airports and ~9.6k cities, which is far more than any model can hold in context. Matches are ranked (exact code, then exact city code, then name), and airports with no scheduled service sort last. Each dataset is downloaded once per process and cached for 24 hours.

find_nearest_airports answers the question lookup_airports cannot: the closest airport is rarely named after the town. It resolves near="Pattaya" against the cached city and airport datasets and ranks by great-circle distance — no third-party geocoder involved.

Errors and empty results

Every response carries status ("ok" or "error"), so an empty data list is never confused with a failure. Bad input is refused before the API call, with the expected format spelled out ("departure_at must be \"YYYY-MM-DD\" or \"YYYY-MM\"…"), and both error and empty responses carry a hint naming what to try next. On a rejected argument the hint says what to substitute and which tool resolves it — enough for the model to fix the call itself on the second attempt.

Setup

Building the image yourself from a checkout, instead of pulling it:

  1. Get an API token at https://www.travelpayouts.com/programs/100/tools/api
  2. Copy .env.example to .env and fill in your token
  3. Build and run with Docker:
docker build -t aviasales-mcp .
docker run --env-file .env aviasales-mcp

Configuration

Variable Required Description
AVIASALES_API_TOKEN Yes Travelpayouts API token
AVIASALES_PARTNER_ID No Partner ID for booking links
AVIASALES_DEFAULT_CURRENCY No Default price currency (default: rub)
AVIASALES_MARKET No 2-letter market whose price cache to read (unset → ru)
AVIASALES_LOCALE No Language of reference data names (default: en)
LOG_LEVEL No Logging level (default: INFO)
MCP_PORT No Serve streamable-HTTP on this port instead of stdio (PORT also accepted)
MCP_HOST No Bind address for HTTP mode (default: 127.0.0.1; use 0.0.0.0 in Docker)
MCP_AUTH_TOKEN No Shared secret required on every HTTP request
MCP_AUTH_ALLOW_QUERY_TOKEN No Also accept the token as ?token= (default: false)
MCP_ALLOW_INSECURE_HTTP No Permit a non-loopback bind with no token (default: false)

AVIASALES_MARKET is worth setting: the price cache is per market, and the same route in the same currency comes back at a different price for ru and us.

HTTP transport

By default the server speaks stdio, which is what local MCP clients expect. Setting MCP_PORT switches it to streamable-HTTP so it can be reached remotely:

docker run --env-file .env \
  -e MCP_PORT=8080 -e MCP_HOST=0.0.0.0 -e MCP_AUTH_TOKEN=<your-secret> \
  -p 8080:8080 aviasales-mcp

The endpoint is then http://<host>:8080/mcp, and every request must present the token as Authorization: Bearer <token>; anything else gets a 401.

Some MCP clients cannot set headers. MCP_AUTH_ALLOW_QUERY_TOKEN=true also accepts ?token=<token>, but note that uvicorn — and any proxy in front of it — writes the full URL to its access log, so the secret ends up in logs. Prefer the header.

There is no TLS here: terminate it at a reverse proxy if the port is reachable from anywhere untrusted.

Without MCP_AUTH_TOKEN the port is unauthenticated and anyone who reaches it can spend your Travelpayouts quota. Loopback binds are allowed (with a warning); binding anything else refuses to start unless you also set MCP_ALLOW_INSECURE_HTTP=true.

Limitations

  • Prices are a cache of recent searches, not live availability. A fare can be gone by the time the link opens; expires_at says when the quote lapses.
  • Prices are always per adult in economy. Passenger count and cabin change the booking link, never the quoted number.
  • No booking. This server searches and links out; it never holds or buys.
  • Cache coverage is uneven. An empty result means nobody searched that route recently, not that the route does not exist.
  • get_latest_prices, the matrices and nearby airports come from the older v2 response shape: they name the selling agency rather than the airline and carry no flight number.
  • Rate limits are per endpoint (600/min for most, 60/min for the week and nearby matrices). The server retries 429s and warns when the published quota runs low.

Development

docker build --target dev -t aviasales-mcp-dev .
docker run --rm aviasales-mcp-dev pytest -q
docker run --rm -v "$(pwd)":/app -w /app aviasales-mcp-dev ruff check src/ tests/

CI runs the suite on Python 3.12, 3.13 and 3.14 plus a Docker image build — see .github/workflows/ci.yml.

Releasing

Pushing a v* tag runs .github/workflows/release.yml: the CI suite, then an sdist and a wheel checked against the tag version and twine check, an upload to PyPI through Trusted Publishing (no token stored anywhere), and finally the GitHub Release with the sdist, wheel, .mcpb and checksums.txt. registry.yml publishes the image and the MCP registry entry from the same tag. version in pyproject.toml and server.json must match the tag.

License

GPL-3.0-or-later — see LICENSE.

Metadata

Release files for aviasales-mcp 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aviasales-mcp 0.5.2
File Size Uploaded
aviasales_mcp-0.5.2.tar.gz 65.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aviasales-mcp 0.5.2
File Interpreter ABI Platform
aviasales_mcp-0.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 111.4 kB

Release files / aviasales_mcp-0.5.2.tar.gz

Download URL aviasales_mcp-0.5.2.tar.gz
Size 65.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e325d447c1be2d9a34685a9bffcb4037648579732aa91887e1287aa110c2f04a
BLAKE2b-256 checksum
How to use checksums
cba93b430606998b7885bb412ac3cbc5b0a746e414eebc568d940b3492ba4d2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / aviasales_mcp-0.5.2-py3-none-any.whl

Download URL aviasales_mcp-0.5.2-py3-none-any.whl
Size 46.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0e25c1595ae5338e6547f2e5f0c297590a78423c817d78a97099a479cf6481c2
BLAKE2b-256 checksum
How to use checksums
a0b2c9112b6a7f59e11f2bdb3415094668044e49dee14214c563352c4270b385
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.3

2 release files

This release

0.5.2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page