openapi: 3.1.0
info:
  title: ALLFLYGHTS MCP Server
  version: 1.0.0
  description: |
    **A geo-based flight search MCP server for AI agents and automated workflows.**

    The ALLFLYGHTS MCP server exposes the same routing engine as the public REST
    API through the Model Context Protocol. AI agents — Claude, Cursor, custom
    LLM applications — can search locations and discover flight routes directly,
    without needing to wrap the REST API themselves.

    ## What it is

    A Model Context Protocol server that exposes ALLFLYGHTS' geo-based flight
    routing as three callable tools:

    - **`search_locations`** — find cities, towns, and airports by name
    - **`get_city`** — look up a single location by GeoNames id
    - **`search_flights`** — search flight routes between one or more legs

    Behind the scenes, every tool call resolves to a request against the public
    REST API. The MCP server is a thin protocol translator — the routing engine,
    NDC integrations, and geo-search live in the backend.

    ## Why MCP

    MCP is the emerging standard for letting AI agents call external services
    through a typed, predictable protocol. Compared to handing an AI raw HTTP
    access, MCP gives you:

    - **Typed inputs and outputs** — agents receive structured data, not free-form text
    - **Tool descriptions** — agents reason about when to call each tool
    - **Standard client support** — Claude Desktop, Cursor, VS Code, and others connect natively
    - **Transport-layer authentication** — bearer tokens, no per-tool wrangling

    ## How it works

    1. Configure your MCP client with the server URL and your access token
    2. The agent reads each tool's description and decides when to call it
    3. Each tool call resolves to a public REST API request
    4. Structured results return to the agent for reasoning

    ## Typical flow

    ```
    User asks an AI agent: "Plan a trip from Tolna to Cambridge"

    1. Agent calls search_locations with query="Tolna"
       → receives location list, picks the city, saves its geonameid
    2. Agent calls search_locations with query="Cambridge"
       → receives location list, picks the city, saves its geonameid
    3. Agent calls search_flights with both geonameids
       → receives route options with airports, airlines, and distances
    4. Agent summarises the options for the user
    ```

    ## Transport

    The server speaks **MCP over Streamable HTTP**. Clients connect to a single
    HTTP endpoint (`POST /mcp`) and exchange MCP messages over a long-lived
    connection. Browser clients are supported via CORS.

    ## Authentication

    All MCP calls require a bearer token in the `Authorization` header. Tokens
    are issued through the ALLFLYGHTS developer dashboard and validated by the
    access-api service on each connection.

    See the [developer documentation](https://www.allflyghts.com/developers/mcp)
    for client configuration examples (Claude Desktop, Cursor, VS Code, custom
    clients) and quick-start guides.

servers:
  - url: https://mcp.allflyghts.com
    description: Production MCP server

security:
  - bearerAuth: []

tags:
  - name: Tools
    description: MCP tools exposed by the ALLFLYGHTS server
  - name: Transport
    description: MCP Streamable HTTP transport endpoint
  - name: Health
    description: Operational endpoints

paths:

  # ═══════════════════════════════════════════════
  #  MCP STREAMABLE HTTP ENDPOINT
  # ═══════════════════════════════════════════════
  /mcp:
    post:
      operationId: mcpRequest
      summary: MCP Streamable HTTP endpoint
      description: |
        The single MCP protocol endpoint. All tool calls, capability negotiation,
        and protocol messages flow through this URL using the MCP Streamable HTTP
        transport.

        You do not normally call this endpoint directly — your MCP client (Claude
        Desktop, Cursor, etc.) handles the protocol. This documentation describes
        the **tools** the server exposes, not the wire protocol itself.

        The tools available are documented below as virtual `/tools/*` paths for
        readability. They are not separate HTTP endpoints — they are MCP tools
        invoked via the standard `tools/call` MCP method against this single
        endpoint.
      tags:
        - Transport
      requestBody:
        required: true
        description: "Standard MCP JSON-RPC message"
        content:
          application/json:
            schema:
              type: object
              description: "MCP JSON-RPC request"
      responses:
        '200':
          description: MCP response (streamed)
        '401':
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal MCP server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    options:
      operationId: mcpCorsPreflight
      summary: CORS preflight for /mcp
      description: |
        Returns CORS headers for browser-capable MCP clients. Allowed origins
        are configured per deployment.
      tags:
        - Transport
      security: []
      responses:
        '204':
          description: CORS preflight OK

  # ═══════════════════════════════════════════════
  #  TOOL: search_locations
  # ═══════════════════════════════════════════════
  /tools/search_locations:
    post:
      operationId: searchLocations
      summary: "Tool: search_locations"
      description: |
        **MCP tool — not a separate HTTP endpoint.**

        Invoke via MCP `tools/call` with `name: "search_locations"`.

        Tool title: *Search Locations*
        Tool description: *Searches the AllFlyghts public API for matching
        cities and airports.*

        The AI agent uses results to resolve user-mentioned places into
        `geonameid` values, which are then passed to `search_flights`.

        **Behavior notes:**
        - Query is trimmed before matching, and must be at least 1 character
        - Results include both cities (`isAirport: false`) and airports (`isAirport: true`)
        - Cities support geo-based multi-airport flight search
        - Airports pin the flight search to a specific IATA
        - For best results with diacritics or partial matches, use a 3-5 character prefix
      tags:
        - Tools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchLocationsInput'
            examples:
              small_town:
                summary: "Hungarian town"
                value:
                  query: "Tolna"
              prefix_search:
                summary: "Prefix match — works with partial names"
                value:
                  query: "reyk"
              airport_iata:
                summary: "Search by IATA code"
                value:
                  query: "LHR"
      responses:
        '200':
          description: Tool result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchLocationsResult'
              examples:
                tolna_result:
                  summary: Result for query=\"Tolna\"
                  value:
                    content:
                      - type: "text"
                        text: "Found 1 location result(s) for \"Tolna\"."
                    structuredContent:
                      query: "Tolna"
                      locations:
                        - geonameid: 48217
                          name: "Tolna"
                          country: "Hungary"
                          iata: null
                          isAirport: false
                          stateCode: null

  # ═══════════════════════════════════════════════
  #  TOOL: get_city
  # ═══════════════════════════════════════════════
  /tools/get_city:
    post:
      operationId: getCity
      summary: "Tool: get_city"
      description: |
        **MCP tool — not a separate HTTP endpoint.**

        Invoke via MCP `tools/call` with `name: "get_city"`.

        Tool title: *Get City*
        Tool description: *Fetches one city or airport by its geoname id from
        the AllFlyghts public API.*

        Useful when the agent already has a `geonameid` from a previous
        `search_locations` call and wants to confirm or display the location
        details. Returns the same shape as a single entry from `search_locations`.
      tags:
        - Tools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GetCityInput'
            examples:
              get_tolna:
                summary: "Fetch by geonameid"
                value:
                  id: 48217
      responses:
        '200':
          description: Tool result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetCityResult'
              examples:
                tolna:
                  summary: "City result"
                  value:
                    content:
                      - type: "text"
                        text: "Loaded Tolna (Hungary) for geoname id 48217."
                    structuredContent:
                      city:
                        geonameid: 48217
                        name: "Tolna"
                        country: "Hungary"
                        iata: null
                        isAirport: false
                        stateCode: null

  # ═══════════════════════════════════════════════
  #  TOOL: search_flights
  # ═══════════════════════════════════════════════
  /tools/search_flights:
    post:
      operationId: searchFlights
      summary: "Tool: search_flights"
      description: |
        **MCP tool — not a separate HTTP endpoint.**

        Invoke via MCP `tools/call` with `name: "search_flights"`.

        Tool title: *Search Flights*
        Tool description: *Searches AllFlyghts routes between one or more
        origin/destination legs.*

        Submit one or more legs (origin → destination pairs) and receive all
        possible airport combinations with airlines and distances.

        **Search types (by number of legs):**
        - 1 leg → one-way
        - 2 legs (same cities reversed) → return
        - N legs → multi-city or open jaw

        **Each leg's `from` and `to` independently support:**
        - `sameCountry: true` — restrict airports to that location's country
        - `fromDate` / `toDate` — flexible date window (YYYY-MM-DD strings)
        - `tripType` — free-form trip type marker (e.g. one-way, return, multi-city)
        - Cities (geo-based multi-airport discovery) or airports (fixed IATA)

        **Optional:** `airlineGroupedPrices: true` groups results by airline,
        returning only itineraries a single carrier can sell as one ticket.

        **Response:** structured flight result — route combinations from the
        public API search.
      tags:
        - Tools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchFlightsInput'
            examples:
              one_way:
                summary: "One-way: Tolna, HU → Cambridge, GB"
                value:
                  legs:
                    - from:
                        cityId: 48217
                        sameCountry: false
                      to:
                        cityId: 12845
                        sameCountry: false
                        fromDate: "2026-04-25"
                        toDate: "2026-05-02"
              return_trip:
                summary: "Return: Cork ↔ Innsbruck"
                value:
                  legs:
                    - from:
                        cityId: 29001
                      to:
                        cityId: 55420
                        fromDate: "2026-06-15"
                        tripType: "return"
              multi_city:
                summary: "Multi-city: 3 legs"
                value:
                  legs:
                    - from:
                        cityId: 11234
                      to:
                        cityId: 33456
                        fromDate: "2026-07-01"
                    - from:
                        cityId: 33456
                      to:
                        cityId: 67890
                        fromDate: "2026-07-05"
                    - from:
                        cityId: 67890
                      to:
                        cityId: 48217
                        fromDate: "2026-07-10"
              airport_to_city:
                summary: "Fixed airport → geo-based city"
                value:
                  legs:
                    - from:
                        cityId: 2647216
                      to:
                        cityId: 55420
                        fromDate: "2026-09-01"
              grouped_by_airline:
                summary: "Single-airline itineraries only"
                value:
                  legs:
                    - from:
                        cityId: 48217
                      to:
                        cityId: 12845
                        fromDate: "2026-04-25"
                  airlineGroupedPrices: true
      responses:
        '200':
          description: Tool result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchFlightsResult'
              example:
                content:
                  - type: "text"
                    text: "Flight search s_20260425_48217_12845 returned 3 flight result(s)."
                structuredContent:
                  search:
                    searchId: "s_20260425_48217_12845"
                    flights: []

  # ═══════════════════════════════════════════════
  #  HEALTH
  # ═══════════════════════════════════════════════
  /health:
    get:
      operationId: healthCheck
      summary: Health check
      description: |
        Returns 200 OK when the MCP server is running. Used by uptime monitors
        and orchestrators. Does not require authentication.
      tags:
        - Health
      security: []
      responses:
        '200':
          description: Server is healthy
          content:
            application/json:
              schema:
                type: object
                required: [ok, service]
                properties:
                  ok:
                    type: boolean
                    example: true
                  service:
                    type: string
                    example: "allflyghts-mcp"

# ═════════════════════════════════════════════════
#  COMPONENTS
# ═════════════════════════════════════════════════
components:

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ALLFLYGHTS MCP token
      description: |
        Bearer token issued through the ALLFLYGHTS developer dashboard.
        Validated against the access-api service.
        See https://www.allflyghts.com/developers/mcp for setup instructions.

  schemas:

    # ── TOOL INPUTS ──────────────────────────────

    SearchLocationsInput:
      type: object
      required: [query]
      properties:
        query:
          type: string
          minLength: 1
          description: |
            City, town, or airport name to search for. Trimmed before matching;
            must contain at least one non-whitespace character.
          example: "Tolna"

    GetCityInput:
      type: object
      required: [id]
      properties:
        id:
          type: integer
          minimum: 1
          description: "GeoNames id of the location"
          example: 48217

    SearchFlightsInput:
      type: object
      required: [legs]
      properties:
        legs:
          type: array
          description: |
            Origin→destination pairs. 1 leg = one-way, 2 legs = return,
            N legs = multi-city / open jaw.
          minItems: 1
          items:
            $ref: '#/components/schemas/FlightRequestLeg'
        airlineGroupedPrices:
          type: boolean
          description: |
            When true, results are grouped by airline — only itineraries
            bookable as a single airline ticket are returned.
          default: false

    FlightRequestLeg:
      type: object
      required: [from, to]
      properties:
        from:
          $ref: '#/components/schemas/FlightRoute'
        to:
          $ref: '#/components/schemas/FlightRoute'

    FlightRoute:
      type: object
      required: [cityId]
      properties:
        cityId:
          type: integer
          minimum: 1
          description: "GeoNames id from search_locations or get_city"
          example: 48217
        sameCountry:
          type: boolean
          description: "Restrict airport search to this location's country only"
          default: false
        fromDate:
          type: string
          description: "Earliest departure date (YYYY-MM-DD)"
          example: "2026-04-25"
        toDate:
          type: string
          description: "Latest departure date (YYYY-MM-DD) — defines flexible date window"
          example: "2026-05-02"
        tripType:
          type: string
          description: "Free-form trip type marker (e.g. one-way, return, multi-city)"
          example: "one-way"

    # ── TOOL RESULTS ─────────────────────────────
    # Every tool result follows the MCP tool-call response shape:
    # { content: [...], structuredContent: {...} }

    SearchLocationsResult:
      type: object
      required: [content, structuredContent]
      properties:
        content:
          type: array
          description: "Human-readable summary of the tool call"
          items:
            $ref: '#/components/schemas/TextContent'
        structuredContent:
          type: object
          required: [query, locations]
          properties:
            query:
              type: string
              description: "Echo of the input query"
            locations:
              type: array
              items:
                $ref: '#/components/schemas/Location'

    GetCityResult:
      type: object
      required: [content, structuredContent]
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/TextContent'
        structuredContent:
          type: object
          required: [city]
          properties:
            city:
              $ref: '#/components/schemas/Location'

    SearchFlightsResult:
      type: object
      required: [content, structuredContent]
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/TextContent'
        structuredContent:
          type: object
          required: [search]
          properties:
            search:
              $ref: '#/components/schemas/FlightsResponse'

    TextContent:
      type: object
      required: [type, text]
      properties:
        type:
          type: string
          enum: [text]
          example: "text"
        text:
          type: string
          example: "Found 1 location result(s) for \"Tolna\"."

    # ── DOMAIN MODELS ────────────────────────────

    Location:
      type: object
      description: |
        A location result — either a city/town or an airport.
        Use `geonameid` as `cityId` when calling search_flights.
      required:
        - geonameid
        - name
        - country
        - isAirport
      properties:
        geonameid:
          type: integer
          description: "GeoNames id — use this as cityId in flight search requests"
          example: 48217
        name:
          type: string
          example: "Tolna"
        country:
          type: string
          example: "Hungary"
        iata:
          type: string
          nullable: true
          description: "IATA code — present only when isAirport is true"
          example: null
        isAirport:
          type: boolean
          description: "True = airport, false = city/town/village"
          example: false
        stateCode:
          type: string
          nullable: true
          description: "State or region code where applicable"
          example: null

    FlightsResponse:
      type: object
      required: [searchId, flights]
      properties:
        searchId:
          type: string
          example: "s_20260425_48217_12845"
        flights:
          type: array
          description: |
            Array of flight result objects. Schema matches the public REST API
            response — see https://www.allflyghts.com/publicapi for the full
            Flight model with airports, airlines, segments, and distances.
          items:
            type: object

    # ── ERROR MODEL ──────────────────────────────

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: "Error message"
          example: "Unauthorized"
