Skip to main content
Glama
README.md
# obilet-mcp 🇹🇷

mcp-name: io.github.mohammad-abbasi-sh/obilet-mcp

Unofficial **read-only** MCP server for obilet.com**, Türkiye's bus & flight ticket search — search bus locations, list all bus journeys for a route + date (partner, bus type, seats, departure/arrival, duration, price in TRY, partner rating, stops), read full journey details, get price statistics, and read the flight summary (flight count, min/max price, airlines) for the same city pair.

> Built by Mohammad Abbasi as part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with obilet.com.

## For AI agents

Add to your MCP client config (stdio):

```json
{
  "mcpServers": {
    "obilet": {
      "command": "uvx",
      "args": ["obilet-mcp"]
    }
  }
}
```

Or run locally: `python -m obilet_mcp.server` (needs `mcp<2`, `curl-cffi`).

## Tools

| Tool | Description |
|------|-------------|
| `obilet_search_locations` | Search locations by name — `query` ("Ankara", "İstanbul"), `kind` ("bus" / "flight"); returns id, name, display name, city id per match (bus ids are ints, flight ids are strings like "250_0") |
| `obilet_search_bus_journeys` | Bus journeys for a route + date — `origin_id` / `destination_id` (bus location ids, e.g. 349 İstanbul Avrupa -> 356 Ankara), `date` "YYYY-MM-DD", `sort` ("price_asc" / "departure" / "duration"), `limit` 1..24; returns journey id, partner, bus type, seats, stations, departure/arrival, duration, price in TRY, partner rating (0-10), stops, plus the total count |
| `obilet_journey_detail` | Full detail of one journey (`journey_id`): partner, bus type, seats, stations, departure/arrival, duration, internet & original price in TRY, service fee, partner rating, bus name, platform, description, every stop with its time |
| `obilet_price_stats` | Price statistics for a route + date: journey count, min / median / mean / max ticket price in TRY |
| `obilet_flight_summary` | Flight summary for a route + date: flight count, min/max price in TRY, min/max duration, first/last departure, direct-flight flag, airlines with IATA codes (individual flight listings are NOT in v1) |

Example output (`obilet_search_bus_journeys` / İstanbul Avrupa -> Ankara, 2026-11-07):

```json
{"origin_id": 349, "destination_id": 356, "date": "2026-11-07",
 "sort": "price_asc", "total": 421, "count": 2, "journeys": [
  {"journey_id": 1432053091, "partner": "Yükseller Aksaray Birlik",
   "bus_type": "2+1", "total_seats": 41, "available_seats": 36,
   "origin": "Esenler Otogarı", "destination": "Ankara (Aşti) Otogarı",
   "departure": "2026-11-07T07:30:00", "arrival": "2026-11-07T15:30:00",
   "duration": "08:00:00", "price_try": 539.0, "original_price_try": 700.0,
   "price_currency": "TRY", "partner_rating": 8.64, …}, …]}
```

Example output (`obilet_flight_summary` / same route & date):

```json
{"origin_id": 349, "destination_id": 356, "date": "2026-11-07",
 "flight_count": 26, "has_direct": true, "min_price_try": 1461.99,
 "max_price_try": 7399.34, "min_duration": "01:00:00",
 "max_duration": "12:05:00", "first_departure": "00:05:00",
 "last_departure": "23:30:00",
 "airlines": [{"name": "Pegasus", "iata": "PC"}, {"name": "AJet", "iata": "VF"},
              {"name": "THY", "iata": "TK"}]}
```

Example output (`obilet_price_stats` / same route & date):

```json
{"origin_id": 349, "destination_id": 356, "date": "2026-11-07",
 "count": 421, "min_try": 539.0, "median_try": 800.0,
 "mean_try": 852.38, "max_try": 1150.0}
```

## How it works

obilet.com exposes a JSON API, but it is session-gated: an HTML page
is fetched first (it sets cookies incl. `ob_Session` and `__cf_bm`),
then POSTs carry `X-Requested-With: XMLHttpRequest` with that page as
Referer. Locations: POST /json/duraklar (bus) or /json/ucak-duraklari
(flight). Journeys: POST /json/journeys/{origin}-{destination}/{date}
with `{}` — one response holds every journey for the route. Detail:
POST /json/sefer-tam/{journeyId}. The same journeys response carries
the flightSummary. Sorting is client-side (price / departure /
duration). The async flights endpoint is broken on the site side
(404/500), so individual flight listings are out of scope for v1.
Transport uses a persistent curl-cffi chrome120 session; requests are
spaced ~0.8s apart; everything is read-only, no login, no API key.

## Install

```bash
uvx obilet-mcp        # run directly (needs uv)
# or
pip install obilet-mcp
obilet-mcp         # starts the MCP server on stdio
```

## Tests

```bash
python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/python -m unittest discover -s tests -v
```

Offline tests run on recorded fixtures (real pages fetched 2026-11-06);
live tests hit obilet.com.

## Roadmap

- Individual flight listings (if the async flights endpoint is fixed)
- Round-trip search (outbound + return in one call)
- Seat-map / available-seat detail per journey

## License

MIT — see [LICENSE](LICENSE).

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation4/5

Each tool has a recognizable role: location lookup, bus journey search, journey detail, aggregate price stats, and flight summary. The main soft overlap is between obilet_price_stats and obilet_search_bus_journeys, since both take route+date and prices already appear in journey results, and price_stats is bus-only while flight_summary also reports price ranges. Descriptions do clarify the distinction (aggregate vs. listings), so confusion is limited.

Naming Consistency4/5

All five tools share the obilet_ prefix and snake_case, which makes the set predictable and easy to scan. The only inconsistency is verb style: search_locations and search_bus_journeys lead with a verb, while price_stats, journey_detail, and flight_summary are noun-first phrases. It is still readable but not a strict verb_noun pattern.

Tool Count5/5

Five tools is well within a healthy range for a read-only travel search domain. Each tool covers a distinct step (resolve locations, list journeys, inspect a journey, aggregate prices, summarize flights) with no filler tools. Nothing feels padded or conspicuously missing at the count level.

Completeness4/5

The surface supports the core workflow: find location ids, search bus journeys, drill into a journey, get price statistics, and get a flight overview. Gaps exist but are documented—no individual flight listings (explicitly broken upstream in v1) and no booking/purchase or advanced filtering on bus results. Agents can work around these within the stated informational purpose.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues