oasa-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@oasa-mcp when is the next bus from Syntagma?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
oasa-mcp
An MCP server for Athens public transport. It gives an AI assistant live bus and trolley arrivals, vehicle positions, routes, stops and timetables for the OASA network, in Greek or Latin script.
Unofficial and unaffiliated with OASA. Data belongs to OASA (Οργανισμός Αστικών Συγκοινωνιών Αθηνών).
What stop_arrivals returns:
ΠΛ. ΠΛΑΤΑΝΟΥ (PL. PLATANOY) · stop 10272 · as of 18:30 (Europe/Athens)
line destination in (min) then eta vehicle
Χ14 ΚΗΦΙΣΙΑ (KIFISIA) 16 — 18:46 44621
500 ΚΗΦΙΣΙΑ (KIFISIA) 52 — 19:22 14439
Minutes are live OASA estimates and shift as vehicles move.and find_stops, given a coordinate:
Stops near 37.9838, 23.7275 (within 300 m)
stop_code name street metres
60134 ΠΛ. ΟΜΟΝΟΙΑΣ (PL. OMONOIAS) ΑΘΗΝΑΣ 60
10184 ΟΜΟΝΟΙΑ (OMONOIAS) — 64
12750 ΣΤ. ΟΜΟΝΟΙΑ — 67
11395 ΟΜΟΝΟΙΑ-ΡΕΞ (OMONOIA - REX) — 72Install
Add it to your MCP client. Nothing to install first — npx fetches it on demand.
{
"mcpServers": {
"oasa": { "command": "npx", "args": ["-y", "oasa-mcp"] }
}
}For Claude Code:
claude mcp add oasa -- npx -y oasa-mcpCheck it works:
npx -y oasa-mcp --self-testRequires Node 20.19 or newer. No API key — OASA's telematics API is public.
Related MCP server: TfL MCP Server
Tools
Tool | Answers |
| When is my bus? Live minutes to arrival, each joined to its line and destination |
| Which stop do I mean? By name or by coordinate, with real distances in metres |
| What can I catch from here? |
| Which line is that? Search ~470 lines by number or name |
| Which direction? The route variants of a line |
| Where does it go? Ordered stops along a route |
| Where is it now? Live GPS, heading, and the age of each report |
| What path does it take? Summary, coordinates or GeoJSON |
| When is the last bus? Timetables by direction and service day |
| Which timetables exist for a line (weekday / Saturday / Sunday, seasonal) |
Names work in either script, with accents and case ignored, so Syntagma, ΣΥΝΤΑΓΜΑ,
Σύνταγμα and syntagma are all the same query. Common English names such as Piraeus
and Airport are mapped too.
When a name matches several stops the tool returns the candidates rather than picking one. Athens stop names repeat across the city, and a wrong stop would produce a confidently wrong arrival time — worse than one extra round-trip.
Scope and limits
Attica only — OASA buses, trolleys, metro and tram. Nothing outside greater Athens.
Empty results are normal. Outside roughly 05:00–00:30 Europe/Athens most lines do not run, so arrivals and vehicle positions legitimately come back empty. That is not an error and the tools say so explicitly.
Arrival minutes are OASA's live estimates, not timetable times. They shift as vehicles move. Use
line_schedulefor scheduled departures.Stop search by name is best-effort, because the API has no stop-search endpoint at all — stops can only be listed per route. Two things cover the gap:
A name is first matched against lines whose names mention it, then against that line's stops. Line names carry the major places, so
Syntagma,Omonia,PiraeusandKifisiaresolve immediately from a few cached requests.Meanwhile a full index is built in the background by walking every route, and everything already fetched is folded into it, so repeated queries get cheaper and more complete.
A minor stop whose name appears in no line name may not be found until that index fills in; the tools say so rather than implying the stop does not exist. Searching by
lat/lonis always exact and immediate.OASA_PREWARM_INDEX=1starts the index at launch.
Why this exists
OASA's telematics API has a reputation for constant downtime. Most of that is a DNS problem, not an outage.
The API is served from several addresses inside OASA's 195.46.22.88/29, steered by a DNS
record with a 60-second TTL. At the time of writing that record points at 195.46.22.91,
which silently drops every packet — all ports time out, 100% ICMP loss — while 195.46.22.94
answers normally and presents the genuine *.oasa.gr certificate. The failover never fired.
A client that trusts DNS therefore hangs on every call, and no amount of retrying, header tweaking or TLS impersonation helps, because the connection attempt is dropped. The only fix is to try a different address.
So this server tries the DNS answer first — it is the intended address and will be correct
again once OASA repairs the record — then falls back across the rest of the block. Fallback
connections still send SNI and Host for telematics.oasa.gr, so TLS must still validate
against OASA's real certificate; verification is never disabled.
If OASA renumbers, OASA_ENDPOINTS overrides the list without waiting for a release.
Configuration
All optional.
Variable | Default | Purpose |
| OASA's | Comma-separated addresses to try when DNS fails |
|
| Override the origin entirely |
| off | Trust DNS only |
| off | Keep the cache in memory only |
|
| Where to persist cached reference data |
|
| Per-request timeout |
|
| In-flight upstream requests |
| off | Build the stop-name index at startup |
|
|
|
Reference data (lines, routes, stops, geometry, timetables) is cached on disk, because an MCP stdio server is spawned fresh per session and the line list alone is 150 KB. If OASA is unreachable, cached reference data is still served and labelled with its age. Live arrivals and vehicle positions are never cached.
Notes on the upstream API
Collected while building this, in case they save someone else the debugging:
HTTPS is mandatory. Port 80 completes the TCP handshake and never replies, so an
http://client hangs instead of failing. The published examples all sayhttp://.getStopArrivalsreturnsnullwhen nothing is due — not[].getBusLocationreturns"", an empty JSON string. Both mean "nothing", and both are routine overnight.Field types drift. Between 2026-09-24 and 2026-10-07
route_code,btime2,StopCodeand others changed from string to number, whileCS_LAT/CS_LNGstayed strings.StopIDkeeps a significant leading zero ("060134") whereStopCodeis60134, so identifiers must never be coerced to numbers.CS_DATEhas two formats:2026-10-07 01:42:42.000now, andSep 24 2026 09:31:34:000AMpreviously — note the milliseconds joined by a colon.getClosestStopstakesp1=latitude,p2=longitude, the opposite of what the community documentation says. Verified by observation.Its
distancefield is in degrees, not metres (0.00059≈ 50 m). This server ignores it and computes metres itself.webRouteDetailsputs longitude inrouted_xand latitude inrouted_y.Greek text contains Latin homoglyphs. Real values include
ΓΚΥΖH(LatinH),ΧΕΙΜΕΡΙΝO(LatinO) andΑNWO ΠΑΤΗSIA. Searching for the correct Greek spelling fails unless you fold these, and it looks like "no such stop" rather than an encoding fault.getScheduleDaysMasterlinereturns a field whose name is the empty string.getDailyScheduleappears defunct —{"come":[],"go":[]}for every line tried. Timetables come fromgetSchedLines, which needs a master-line code, a service-day code and a line code.Service-day codes are seasonal (currently
WINTER …), so they must be looked up per line rather than hardcoded.Errors arrive as HTTP 200 with an
{"error": "..."}body.
Actions this server does not wrap: webGetLinesWithMLInfo, getLinesAndRoutesForMl,
getRoutesForLine, getMLName, getLineName, getRouteName, getDailySchedule,
webGetLangs. The community API reference
documents them, with the caveats above.
Development
npm install
npm run typecheck
npm test # offline, runs against recorded fixtures
npm run build
npm run test:live # opt-in; hits the real API
npm run record # re-record fixtures from the live APIThe unit suite never touches the network: an accidental real request throws. Fixtures in
test/fixtures/ are recorded live, so tests assert against the real service rather than a
guess. The live suite asserts invariants rather than values and skips on an upstream outage,
so a red build always means a real regression.
Licence
MIT
Available Tools
10 toolsfind_stopsFind stopsARead-only
Find OASA stops by name or by coordinate. Names may be in Greek or Latin characters ("Syntagma", "ΣΥΝΤΑΓΜΑ", "syntagma"); accents, case and common English spellings such as "Piraeus" all work. Passing lat and lon is the fastest and most reliable route and returns true straight-line distances in metres. If a name matches several stops, the candidates are returned rather than one being guessed, because Athens stop names repeat across the city. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: lat, lon, line, limit, +3 more.
line_routesRoutes of a lineARead-only
The direction variants (routes) of one OASA line, with destination and length. A line usually has an outbound and a return route, and often extra variants for night service, weekends or diversions. Other tools need the route_code from here. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: line (required), language.
line_scheduleTimetable for a lineARead-only
Scheduled departure times for one OASA line, by direction and service day — the tool for "when is the first/last bus". Defaults to the service day matching today in Athens. Departures after midnight are flagged next_day and sorted after the late-evening ones. Circular lines have only one direction. These are timetable times, not live estimates; use stop_arrivals for what is actually coming. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: line (required), language, direction, service_day, +1 more.
line_service_daysService days of a lineARead-only
The timetable variants (service days) published for one OASA line, such as weekday, Saturday and Sunday. These codes are seasonal — the current set is the winter one — so they must be read rather than assumed. Feed a schedule_day_code from here into line_schedule for an exact timetable. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: line (required), language.
list_linesList transit linesARead-only
List or search OASA lines. Match by the rider-facing number ("040", "Α1", "X95", matched exactly) or by name in Greek or Latin characters. There are around 470 lines, so always pass a query unless you genuinely need a sample; the total is reported so you can tell whether to narrow. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: limit, query, language.
routes_at_stopRoutes serving a stopARead-only
Which OASA lines and routes call at a given stop — the "what can I catch from here" question. Accepts a stop name in either script or an exact stop_code. This is scheduled network data, not live: use stop_arrivals for actual waiting times. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: stop (required), language, include_hidden.
route_shapeRoute geometryARead-only
The geographic path of one OASA route. Defaults to a summary (length, bounding box, endpoints) because raw shapes run to hundreds of points; request "coords" or "geojson" only when the geometry itself is needed, and it will be simplified to the requested point budget with endpoints preserved. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: route_code (required), format, max_points.
route_stopsStops along a routeARead-only
The ordered list of stops along one OASA route, with coordinates. Needs a route_code from line_routes — a line has several routes and each has its own stop sequence. Long routes have 90+ stops, so results are paged. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: route_code (required), limit, offset, language.
stop_arrivalsLive arrivals at a stopARead-only
Live next-vehicle arrivals for one OASA stop, in minutes, with each vehicle joined to its line number and destination. Accepts a stop name in Greek or Latin characters ("Syntagma", "ΣΥΝΤΑΓΜΑ") or an exact stop_code. If a name matches several stops the candidates are returned instead of one being guessed. An empty result is normal and not an error — outside roughly 05:00-00:30 Europe/Athens most lines are not running. Minutes are OASA's own live estimates, not timetable times; use line_schedule for scheduled departures. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: stop (required), line, limit, language.
vehicle_positionsLive vehicle positionsARead-only
Current GPS positions of OASA vehicles on a route or line, with heading and the age of each report. Positions are typically seconds old, but a vehicle that has stopped reporting is flagged stale — check age_seconds before presenting a position as current. An empty result outside service hours is normal. Covers Attica (greater Athens) only: OASA buses, trolleys, metro and tram.
Inputs: line, language, route_code.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 10 tools
Each tool targets a distinct transit resource or action: stop lookup, live arrivals, routes at a stop, line search, line route variants, route stop sequence, vehicle GPS, route geometry, scheduled departures, and service-day variants. Overlaps such as live arrivals versus scheduled departures are explicitly clarified in descriptions, and route/line/stop concepts are separable by task.
All names use consistent snake_case and are readable, but the set mixes verb-first names (find_stops, list_lines) with noun-first names (stop_arrivals, route_stops, line_schedule). This is a minor deviation rather than a chaotic mix, so the pattern is mostly predictable.
Ten tools are well-scoped for a transit-data server, covering stop search, real-time arrivals, network topology, schedules, and vehicle positions without obvious filler. Each tool has a clear role in the core workflow.
The surface covers the main OASA query lifecycle: finding stops, checking arrivals, exploring lines/routes, route geometry, vehicle positions, and schedules. Minor gaps remain around journey planning, service alerts/disruptions, and fare information, but core transit lookup workflows are complete.
Related MCP Connectors
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
Mobility, geocoding and nearby urban services for AI agents.
Read-only public transit departures, stop search, and city coverage for bus and train users.
Provide real-time transportation data including bus arrivals, train service alerts, carpark availa…
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to access real-time Transport for London data, including tube/bus arrivals, line status, journey planning, and disruptions.1-
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to query live Transport for London data, including service status, arrivals, journey planning, bike points, crowding, and fares.1MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to access real-time public transport data for Moscow and the Moscow region, aggregating and reconciling information from multiple sources. Provides tools for finding stops, live arrival boards, vehicle tracking, route details, and ETA calculations.MIT
- AlicenseAqualityCmaintenanceEnables AI assistants to access real-time and scheduled public transit data, including routes, stops, departures, and service alerts, across multiple US metropolitan areas.5MIT