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-mcpwhen 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 for Poke
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.
| Name | Required | Description | Default |
|---|---|---|---|
| lat | No | Latitude; must be paired with lon. | |
| lon | No | Longitude; must be paired with lat. | |
| line | No | Restrict to stops served by this line (number such as "040", or a name). | |
| limit | No | ||
| query | No | Stop name, in Greek or Latin characters. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
| radius_m | No | Search radius in metres; only used with lat/lon. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint and openWorldHint already present, the description still adds real behavior: multiple matches are returned rather than one guessed, distances are true straight-line metres, and coverage is limited to Attica (OASA buses, trolleys, metro, tram). Error handling and rate limits are not covered, but the multi-match and distance semantics are the traits an agent most needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four sentences, each front-loaded with the operative fact: what it does, name-matching tolerance, preferred input mode, ambiguity/coverage caveat. No sentence is filler and the most decision-relevant guidance (use lat/lon) comes early.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must carry return behavior, and it does explain that candidate sets (not a single guess) come back with straight-line distances. The geographic boundary is stated. It stops short of describing the fields in each returned stop record, which an agent composing a follow-up call would find useful.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 86%, so the baseline is 3, but the description meaningfully extends it: the query parameter tolerates Greek or Latin script, ignores accents and case, and accepts common English spellings like 'Piraeus'. It also reinforces that lat/lon is a paired, faster path. It adds nothing for line, limit, or radius_m beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb (find) and resource (OASA stops) plus the two supported lookup modes (name, coordinate). The resource is clearly distinct from every sibling, which deal with arrivals, routes, lines and vehicles rather than stop lookup. Scope is further pinned down with the operator/geography sentence.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states which lookup mode to prefer ('Passing lat and lon is the fastest and most reliable route') and explains why ambiguity is handled by returning candidates instead of guessing. It also gives an explicit geographic exclusion ('Covers Attica (greater Athens) only'). It does not, however, name sibling tools for follow-up needs such as arrivals at a found stop.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| line | Yes | Line number ("040"), line_code, or line name in either script. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuine domain context beyond that: the geographic coverage limit (Attica only, OASA buses/trolleys/metro/tram) and the typical outbound/return/night/weekend route structure. It does not describe return format or ordering, but with annotations present the bar is lower.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with what is returned, followed by the domain context and the scope limit. Every sentence earns its place with no repetition of the title or schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter lookup with no output schema, the description covers purpose, scope, and one key return field (route_code) that downstream tools consume. It could say a bit more about the returned shape (e.g., how routes are identified), but nothing essential to calling it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with both parameters documented in-schema, including the accepted identifier forms and the language enum. The description adds no additional parameter detail (it never mentions the language option), so baseline 3 is correct when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource ('the direction variants (routes) of one OASA line') and even previews the payload ('with destination and length'). This distinguishes it from siblings like route_stops, route_shape, and list_lines, which an agent can rule out without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a concrete routing cue: 'Other tools need the route_code from here,' which tells the agent this is a prerequisite step in a chain. It also scopes applicability ('Covers Attica (greater Athens) only'). It stops short of naming an explicit alternative or when-not-to-use condition.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| line | Yes | Line number ("040"), line_code, or line name. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
| direction | No | go = outbound, come = return. | both |
| service_day | No | Which timetable to read. "today" uses the current day in Athens. | today |
| schedule_day_code | No | An exact code from line_service_days; overrides service_day. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint and openWorldHint, and the description adds genuinely useful behavior: defaulting to today's service day in Athens, next_day flagging with sort ordering after late-evening departures, and single-direction behavior for circular lines. It does not describe pagination or response shape, but no output schema exists to cover that either.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and routing before the behavioral details; every sentence carries information. Slightly dense in the middle with the next_day/sorting and circular-line details, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only timetable tool with no output schema, the description covers defaults, the next_day flag semantics, sort order, and the Attica/OASA coverage boundary — enough for correct invocation. It could say more about the response shape for direction='both', which is the only real gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and all five parameters — including enum meanings for direction, language and service_day — are documented in the schema itself. The description adds only marginal meaning (Athens-local default for 'today'), so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: 'Scheduled departure times for one OASA line, by direction and service day.' It names the primary use case ('when is the first/last bus') and clearly distinguishes itself from sibling stop_arrivals and from live-tracking tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: use stop_arrivals for live estimates rather than timetable times, and points to line_service_days as the source of schedule_day_code. Both a when-to-use and a when-to-use-something-else are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| line | Yes | Line number ("040"), line_code, or line name. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true. The description adds genuinely useful non-annotation behavior: the codes are seasonal (the current set is the winter one) and must be read, not assumed, plus a hard geographic coverage limit. It stops short of describing return volume, ordering, or labeling, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with what is returned, then the downstream usage rule, then the coverage constraint. Every sentence carries information an agent needs; nothing is redundant with the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description must explain returns — it does so by naming the output concept and its field (schedule_day_code) and giving example values. The seasonal-caveat and coverage limits are covered. Minor gaps remain around the exact shape of each returned variant (labels, holiday variants), but the essentials for correct invocation are present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for both parameters, so the schema already carries the semantics (line accepts number/code/name; language enum controls Greek/English pairing). The description adds no parameter-level syntax or format guidance beyond mentioning schedule_day_code, which belongs to line_schedule's input, not this tool's. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: returns the timetable variants (service days) published for one OASA line, with concrete examples (weekday, Saturday, Sunday). It is clearly distinguishable from sibling line_schedule, which consumes its output rather than producing it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: feed a schedule_day_code from here into line_schedule for an exact timetable. It also warns that codes are seasonal and must be read rather than assumed, and states the scope (Attica only: OASA buses, trolleys, metro, tram) so an agent knows when this tool does not apply.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | No | Line number or name fragment. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, openWorld), but the description adds real operational context: exact-match semantics for the number, ~470 total lines, the reported total, and a hard geographic boundary (Attica only, OASA buses/trolleys/metro/tram). It omits pagination/ordering behavior, which is the main remaining unknown.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three front-loaded sentences: purpose, matching rules, then scoping/caution. Each sentence carries distinct information (verb, match keys, size warning, coverage boundary) with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully notes the total is reported, and it bounds scope to Attica. Minor gaps remain around result ordering/pagination, but for a 3-param read-only discovery tool this is essentially complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 67%, so the schema documents query and language but leaves limit bare. The description adds genuine meaning by specifying that matching is exact on the number and supports Greek or Latin names, which is not derivable from the schema alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("List or search OASA lines") plus the two match keys (rider-facing number vs. name). This clearly separates it from siblings like find_stops, line_routes and line_schedule, which operate on stops or a single line's structure.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit operating guidance: "always pass a query unless you genuinely need a sample", and explains the total-count signal for deciding to narrow. It does not, however, route the agent to a sibling (e.g. line_routes for a line's path once the code is known), so it stops short of full when/when-not/alternatives guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| stop | Yes | Stop name (Greek or Latin characters) or stop_code. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
| include_hidden | No | Include routes OASA marks as hidden from public display. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds genuinely useful behavioral context beyond that: the data is scheduled rather than live, and coverage is limited to greater Athens OASA modes. It does not mention result ordering or volume limits, keeping it short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the purpose, then the live-vs-scheduled caveat, then the coverage boundary. No filler and each sentence carries distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, but the description conveys what comes back (lines/routes serving the stop) and what the data represents (schedule, not real-time). The mode/geography limit is stated, which is the main completeness risk. Minor gap: no mention of result shape or pagination for a potentially long list.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so all three parameters are already documented in the schema. The description reinforces the 'stop' parameter's accepted forms (name in either script or exact stop_code) but says nothing about 'language' or 'include_hidden', so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('which OASA lines and routes call at a given stop') and frames the user intent ('what can I catch from here'), which cleanly separates it from find_stops and stop_arrivals. The scope (Attica, OASA buses/trolleys/metro/tram) further pins down what it returns.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent away from the wrong sibling: 'This is scheduled network data, not live: use stop_arrivals for actual waiting times.' It also states the geographic/mode boundary, so an agent knows when this tool will not help.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | summary = statistics only; coords = [lat, lon] pairs; geojson = LineString. | summary |
| max_points | No | Point budget when returning geometry. | |
| route_code | Yes | Route code from line_routes. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so safety is covered. The description adds genuinely useful behavior beyond that: geometry is simplified to the requested point budget, endpoints are preserved, and only Attica (OASA buses, trolleys, metro, tram) is covered. It stops short of saying what happens for out-of-coverage or unknown route codes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the identity of the resource, followed by the default-vs-override guidance. No filler; every clause carries actionable information (default rationale, override condition, simplification behavior, geographic scope).
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries the burden of explaining returns, and it does so at a high level (summary stats, coord pairs, GeoJSON LineString). Coverage limits and simplification are stated; edge-case behavior (invalid route code, non-Attica routes) is left implicit, which is a minor gap for a 3-param read tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: it explains why 'summary' is the default, that max_points only applies to geometry requests, and that simplification preserves endpoints. This clarifies the interaction between format and max_points rather than merely restating them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('The geographic path of one OASA route') and immediately scopes it by operator and geography. It is clearly distinguishable from siblings like route_stops and line_routes, which deal with stop sequences rather than geometry.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly prescribes the default ('Defaults to a summary ... because raw shapes run to hundreds of points') and the condition for overriding it ('request "coords" or "geojson" only when the geometry itself is needed'). This is exactly the when-to-use guidance an agent needs to avoid over-fetching.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| offset | No | Skip this many stops. | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
| route_code | Yes | Route code from line_routes. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint. The description adds genuinely useful context beyond those: results are paged ('Long routes have 90+ stops, so results are paged') and coverage is geographically limited to Attica. It doesn't mention error behavior for an invalid route_code, which would earn a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each carrying distinct information (scope, prerequisite, paging/coverage), with the core purpose front-loaded. Slightly dense in the middle but no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description appropriately notes that coordinates are returned and that results are paged. Combined with the annotations, an agent has enough to call it correctly, though it doesn't cover what happens with an invalid or out-of-area route_code.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, so most parameters are partly documented structurally. The description adds meaning beyond the schema by explaining route_code's source ('from line_routes') and that pagination exists, explaining limit/offset's purpose. The 'language' parameter is left to its schema description, which is acceptable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource and scope: 'ordered list of stops along one OASA route, with coordinates.' This clearly distinguishes it from siblings like find_stops (name lookup) and routes_at_stop (the inverse direction). The verb+resource+ordering are all explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a prerequisite and provenance: 'Needs a route_code from line_routes — a line has several routes and each has its own stop sequence,' which tells the agent to obtain the code first and why a line isn't sufficient. It does not contrast with alternative stop-listing tools or state when-not to use it, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| line | No | Disambiguate the stop by a line that serves it, and filter to that line. | |
| stop | Yes | Stop name (Greek or Latin characters) or an exact numeric stop_code. | |
| limit | No | ||
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint, but the description adds real operational context: minutes are live OASA estimates rather than timetable times, an empty result is normal rather than an error with the reason given, multi-match names return candidates instead of a guess, and coverage is Attica-only. This is disclosure well beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose and scope lead, followed by input handling, empty-result semantics, and the sibling alternative. Five dense sentences with no filler; every clause carries information an agent could act on.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still describes the return content (per-vehicle line number and destination, in minutes) and the empty/ambiguous cases. Geographic and time-window limits round out what an agent needs to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, and the description reinforces the stop parameter's dual form (Greek/Latin name or exact stop_code) and the ambiguity/candidate behavior, which the schema does not state. It adds nothing for limit or language, but those are partly documented in the schema, so the marginal gain is real if partial.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Live next-vehicle arrivals for one OASA stop, in minutes') plus the shape of each returned item (vehicle joined to line number and destination). It also scopes coverage to Attica and OASA modes, which no sibling covers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to the alternative ('use line_schedule for scheduled departures') and explains when an empty result is expected (outside roughly 05:00-00:30 Europe/Athens). It also tells the agent how to disambiguate via stop name vs. exact stop_code, which is exactly the when-to-use decision an agent needs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| line | No | A line number or name; fans out over its routes (up to 6). | |
| language | No | Which name language to show. 'both' pairs the Greek name with the English. | both |
| route_code | No | A single route code from line_routes. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, so the safety profile is covered. The description adds genuine behavioral detail beyond that: stale-vehicle flagging, the typical freshness window, empty-result semantics outside service hours, and the geographic/operator coverage boundary.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three dense sentences, front-loaded with the core resource and payload, then the staleness caveat, then coverage limits. No filler and nothing repeated from structured fields.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully names the returned fields (heading, age) and explains the staleness/empty-result semantics an agent needs to interpret output correctly. Coverage limits complete the picture for a no-required-parameter tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description confirms the 'route or line' scoping but adds no syntax, format, or interaction detail beyond what the schema already documents (including the up-to-6 route fan-out).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Current GPS positions of OASA vehicles on a route or line') and enumerates the payload fields (heading, report age). It is clearly distinguishable from siblings like stop_arrivals or routes_at_stop, which deal with stops rather than live vehicle telemetry.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives real usage conditions: check age_seconds before presenting a position as current, and treat empty results outside service hours as normal. It also scopes applicability geographically (Attica only). It stops short of explicitly naming sibling tools as alternatives for stop-based queries, so it's clear context without full when/when-not routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
10 tool updates
v0.1.0- First observed
find_stops - First observed
line_routes - First observed
line_schedule - First observed
line_service_days - First observed
list_lines - First observed
route_shape - First observed
route_stops - First observed
routes_at_stop - First observed
stop_arrivals - First observed
vehicle_positions
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.
Maintenance
Related MCP Connectors
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
The Ferryhopper MCP server is a connector for LLMs and AI Agents in maritime travel that exposes ferry routes, schedules, and booking options. It enables AI assistants to search ports and connections across 33 countries and 190+ ferry operators, provide real-time ferry itineraries with indicative prices, and assist users with planning island-hopping or multi-leg journeys by processing natural language queries about ferry times, passenger counts, and travel durations.
Mobility, geocoding and nearby urban services for AI agents.
Read-only public transit departures, stop search, and city coverage for bus and train users.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides real-time access to İzmir's public transportation data, including bus locations, schedules, and route information for ESHOT, İZBAN, Metro, and Tram services. It enables AI assistants to perform transit queries, calculate fares, and locate nearby stations using the Model Context Protocol.4 npm1ISC
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to access real-time Transport for London data, including tube/bus arrivals, line status, journey planning, and disruptions.1-
- AlicenseNot gradedqualityAmaintenanceEnables 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