@cyanheads/onebusaway-mcp-server
Query stops, routes, real-time arrivals, vehicle positions, and schedules from OneBusAway transit APIs via MCP. STDIO or Streamable HTTP.
16 Tools โข 2 Resources
Overview
Real-time transit data and schedules from OneBusAway. It defaults to the Puget Sound instance (King County Metro, Sound Transit, Pierce Transit, Community Transit, and more) and works with any other OneBusAway instance. Find stops and routes, track live arrivals and vehicle positions, and pull full-day schedules, vehicle blocks, and service alerts. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|
onebusaway_list_agencies | List the transit agencies on the instance, with IDs, contact info, and coverage area |
onebusaway_find_stops | Find stops near a lat/lon, optionally filtered by stop code |
onebusaway_search_stops | Resolve a stop name or code to a stop ID |
onebusaway_get_stop | Fetch one stop by ID |
onebusaway_find_routes | Find routes near a lat/lon, optionally filtered by name or number |
onebusaway_search_routes | Resolve a route name or number to a route ID |
onebusaway_get_route | Fetch one route by ID |
onebusaway_list_routes_for_agency | List every route an agency operates |
onebusaway_get_arrivals | Real-time arrivals and departures at a stop, with schedule deviation, vehicle positions, and active alerts |
onebusaway_get_stop_context | Stop details, real-time arrivals, and full detail for every alert at the stop, from one upstream request |
onebusaway_get_alert | Full service alert detail by situation ID |
onebusaway_get_trip | Real-time status and stop sequence for a trip |
onebusaway_get_block | Every trip one vehicle runs in a service day, in order, with stop times |
onebusaway_get_vehicles | Real-time positions of an agency's active vehicles, optionally for one route |
onebusaway_get_schedule_for_stop | Full-day departure schedule for a stop, by route and direction |
onebusaway_get_schedule_for_route | Full-day schedule for a route: every trip and its stop sequence |
Resources
| Resource | Description |
|---|
onebusaway://stop/{stopId} | Stop metadata: name, coordinates, served routes, wheelchair accessibility |
onebusaway://route/{routeId} | Route metadata: short name, description, agency, schedule URL |
The same data is available to tool-only clients through onebusaway_get_stop and onebusaway_get_route.
Capability reference
- No input; returns every agency with
id, contact info, timezone, and coverageCenter / coverageSpan
limitExceeded flags an upstream-capped list, with no pagination to fetch the rest
lat / lon required; radius in meters, default 300, max 1600; optional query matches the stop code printed on the sign
- Each stop carries
id, code, direction, routeIds, and wheelchairBoarding (ACCESSIBLE / NOT_ACCESSIBLE / UNKNOWN); limitExceeded means more stops exist within the radius
query (stop name fragment or stop code) required; maxCount up to 100, default 10
- Same stop shape as
onebusaway_find_stops; limitExceeded means more stops matched than maxCount
- Single
stopId; returns name, code, coordinates, direction, routeIds, and wheelchairBoarding
- Unknown IDs fail as
stop_not_found, with recovery via onebusaway_find_stops or onebusaway_search_stops
lat / lon required; radius in meters, default 500, max 1600, or a latSpan + lonSpan box (both set) in its place; optional query by route name or number
- Each route carries
shortName, longName, agencyId, GTFS type (0=tram โฆ 5=cable_car), color, and schedule url; limitExceeded means more routes exist in the area
query (route name or number) required; maxCount up to 100, default 10
- Returns
shortName, longName, agencyId, and GTFS type; limitExceeded means more routes matched than maxCount
- Fails as
endpoint_unavailable on instances whose route-search endpoint returns 404, Puget Sound among them; use onebusaway_find_routes or onebusaway_list_routes_for_agency instead
- Single
routeId; returns shortName, longName, description, agency, GTFS type, color, and schedule url
- Unknown IDs fail as
route_not_found, with recovery via onebusaway_find_routes or onebusaway_search_routes
agencyId required; unknown agencies fail as agency_not_found
- Every route with
shortName, longName, GTFS type, color, and url; limitExceeded flags an upstream-capped list with no pagination
stopId required; the window is minutesBefore (integer 0โ60, default 5) / minutesAfter (integer 0โ240, default 35), with longer horizons left to onebusaway_get_schedule_for_stop; unknown stops fail as stop_not_found
- Each arrival carries
predicted (false = schedule-only), scheduleDeviation in seconds (positive = late, meaningful only when predicted), predictedArrivalTime, vehiclePosition, stopsAway, and tripId
- Active alerts arrive in
situations[]: those on the stop itself plus those linked from each arrival's situationIds, each once
onebusaway_get_stop_context tool
- Same input as
onebusaway_get_arrivals, and the same stop_not_found / rate_limited failures; one call issues one upstream request
- Returns
stop (the onebusaway_get_stop fields minus routeIds), arrivals in the onebusaway_get_arrivals shape, and alerts in the onebusaway_get_alert shape โ every alert on the stop or on an arrival in the window, including stop-wide alerts no arrival in the window carries
- When the upstream response omits the stop,
stop is null; a referenced alert missing from the response is left out; either way a notice names the tool to fetch it with
- Single
situationId, from onebusaway_get_arrivals (situations[].id or arrivals[].situationIds); unknown IDs fail as situation_not_found
- Returns a TPEG
reason code, severity, consequenceMessage, affects (agency, route, stop, or trip scope), consequences with diversion stop IDs, and activeWindows
tripId required; serviceDateMs (non-negative integer, midnight local) only for a trip on a previous service day; includeSchedule (default true) adds the stop sequence with GTFS times and distanceAlongTripMeters
status carries phase (e.g. in_progress, layover_before), predicted, position, scheduleDeviation, and nextStop; blockId (null when the trip has none) feeds onebusaway_get_block
- Fails as
trip_not_found when the trip isn't active for the service date; a completed trip's times come from onebusaway_get_schedule_for_route
- Single
blockId, from onebusaway_get_trip; unknown IDs fail as block_not_found
- The vehicle's trips for the service day in order, each with
distanceAlongBlock, accumulatedSlackTime (layover seconds), and blockStopTimes; activeServiceIds / inactiveServiceIds show which service calendars apply
agencyId required, unknown agencies fail as agency_not_found; optional routeId is filtered client-side after all of the agency's vehicles are fetched
- Each vehicle carries
position, orientation, phase, scheduleDeviation, tripId, nextStop, and predicted (reporting real-time GPS); limitExceeded flags an upstream-capped list with no pagination
stopId required; optional date as a real YYYY-MM-DD calendar date, default (omitted or blank) today in the agency's timezone; unknown stops fail as stop_not_found
- Departures grouped by route and direction, each with
scheduledDepartureTime and tripId
- Static schedule only; live predictions come from
onebusaway_get_arrivals
routeId required; optional date as a real YYYY-MM-DD calendar date, default (omitted or blank) today; unknown routes fail as route_not_found
- Every trip that day with
tripId, tripHeadsign, serviceId, and its stop sequence
- Static schedule only; live predictions come from
onebusaway_get_arrivals at a stop
onebusaway://stop/{stopId} resource
- Stop record as
application/json, the same shape onebusaway_get_stop returns
stopId comes from onebusaway_find_stops or onebusaway_search_stops
onebusaway://route/{routeId} resource
- Route record as
application/json, the same shape onebusaway_get_route returns
routeId comes from onebusaway_find_routes or onebusaway_search_routes
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
OneBusAway-specific:
- Wraps
onebusaway-sdk with typed error classification (NotFound, RateLimited, ValidationError for an upstream 400, ServiceUnavailable)
- Defaults to the Puget Sound instance (
api.pugetsound.onebusaway.org), where ONEBUSAWAY_API_KEY=TEST works for development; ONEBUSAWAY_BASE_URL points it at any other OneBusAway instance
- Stop and route IDs are agency-prefixed,
{agencyId}_{localId} (stop 1_75403, route 1_100259); agency IDs are the bare prefix (1 for Metro Transit, 40 for Sound Transit)
- One shared pacer, sized by
ONEBUSAWAY_RATE_LIMIT_*, queues every upstream request against the API key's budget; a call that gets no slot within the wait cap fails as retryable rate_limited with data.retryAfter, on any tool
- Transit data only, no trip planning; server-level instructions walk agents through the ID format and the common lookup chains
Agent-friendly output:
predicted on every arrival, trip, and vehicle separates GPS-tracked data from schedule-only projections
- Machine-readable times:
scheduleDeviation in seconds; arrival, stop-schedule, and update timestamps in Unix milliseconds; trip, route-schedule, and block stop times in GTFS seconds from midnight
- Chainable IDs:
stopId from the stop tools feeds arrivals, tripId feeds onebusaway_get_trip, blockId feeds onebusaway_get_block, situationIds feed onebusaway_get_alert, and agencyId feeds vehicles and route listing
- Typed error contracts whose recovery hints name the next tool to call, plus a
notice on empty or truncated results
Getting started
Public Hosted Instance
A public instance is available at https://onebusaway.caseyjhand.com/mcp โ no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"onebusaway-mcp-server": {
"type": "streamable-http",
"url": "https://onebusaway.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file. ONEBUSAWAY_API_KEY=TEST works on the Puget Sound instance without registration.
{
"mcpServers": {
"onebusaway-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/onebusaway-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"ONEBUSAWAY_API_KEY": "TEST"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"onebusaway-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/onebusaway-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"ONEBUSAWAY_API_KEY": "TEST"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"onebusaway-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "ONEBUSAWAY_API_KEY=TEST",
"ghcr.io/cyanheads/onebusaway-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 ONEBUSAWAY_API_KEY=TEST bun run start:http
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- A OneBusAway API key.
TEST works on the Puget Sound instance for development; for production use or other instances, register at the relevant agency's developer portal.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/onebusaway-mcp-server.git
- Navigate into the directory:
- Install dependencies:
- Configure environment:
Configuration
| Variable | Description | Default |
|---|
ONEBUSAWAY_API_KEY | OneBusAway API key. TEST works on the Puget Sound instance. | TEST |
ONEBUSAWAY_BASE_URL | Base URL of the OneBusAway instance. | https://api.pugetsound.onebusaway.org |
ONEBUSAWAY_RATE_LIMIT_REQUESTS | Upstream requests allowed per window, shared by all callers. | 20 |
ONEBUSAWAY_RATE_LIMIT_WINDOW_MS | Width of the sliding rate window, in ms. | 60000 |
ONEBUSAWAY_RATE_LIMIT_MAX_WAIT_MS | Longest a call waits for a slot before failing as rate_limited, in ms. Keep it under the SDK's 60 s request timeout. | 45000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, notice, warning, error). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
Docker
docker build -t onebusaway-mcp-server .
docker run --rm -e ONEBUSAWAY_API_KEY=TEST -p 3010:3010 onebusaway-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/onebusaway-mcp-server. OpenTelemetry peer dependencies are installed by default โ build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|
src/index.ts | createApp() entry point: registers tools and resources, sets server instructions, inits the OneBusAway service. |
src/config | Server-specific env var parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) plus the schemas and format helpers they share. |
src/mcp-server/resources | Stop and route resource definitions (*.resource.ts). |
src/services/onebusaway | OneBusAway service: wraps onebusaway-sdk, paces upstream requests, classifies errors; domain types. |
tests/ | Vitest tests for the tools, resources, service, and config. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches โ no
try/catch in tool logic
- Use
ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
- Register new tools and resources in the
createApp() arrays in src/index.ts
- Wrap external API calls: validate raw โ normalize to domain type โ return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 โ see LICENSE for details.
Transit data from the default Puget Sound OneBusAway API, operated by Sound Transit and King County Metro, is governed by the Sound Transit Transit Data Terms of Use, and users of the hosted endpoint receive it under those terms. Key obligations:
- Clause 2: usage metrics are available on request.
- Clause 3: data is fetched live from the OneBusAway API and is not modified or cached beyond the request cycle.
- Clause 4: you agree to pass substantially similar terms through to any users you provide this data to.
- Clause 7: this server does not use Sound Transit trademarks in its name or branding.