
Connects Claude to OneBusAway transit APIs for real-time vehicle tracking and schedule lookups. You get 15 tools covering stop discovery by location or name, live arrival predictions with GPS positions and schedule deviations, full trip sequences, agency-wide vehicle positions, and static timetables. The arrivals endpoint distinguishes GPS-tracked predictions from schedule-only data and surfaces active service alerts inline. Useful for building transit assistants that need to answer "when's my bus" with actual vehicle locations, not just printed schedules. Works over stdio or streamable HTTP, with a public hosted instance at onebusaway.caseyjhand.com/mcp if you don't want to run it locally. Puget Sound is the reference region, but any OneBusAway deployment works with the right API key.
Query stops, routes, real-time arrivals, vehicle positions, and schedules from OneBusAway transit APIs via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://onebusaway.caseyjhand.com/mcp
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 |
| 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.
onebusaway_list_agencies toolid, contact info, timezone, and coverageCenter / coverageSpanlimitExceeded flags an upstream-capped list, with no pagination to fetch the restonebusaway_find_stops toollat / lon required; radius in meters, default 300, max 1600; optional query matches the stop code printed on the signid, code, direction, routeIds, and wheelchairBoarding (ACCESSIBLE / NOT_ACCESSIBLE / UNKNOWN); limitExceeded means more stops exist within the radiusonebusaway_search_stops toolquery (stop name fragment or stop code) required; maxCount up to 100, default 10onebusaway_find_stops; limitExceeded means more stops matched than maxCountonebusaway_get_stop toolstopId; returns name, code, coordinates, direction, routeIds, and wheelchairBoardingstop_not_found, with recovery via onebusaway_find_stops or onebusaway_search_stopsonebusaway_find_routes toollat / 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 numbershortName, longName, agencyId, GTFS type (0=tram … 5=cable_car), color, and schedule url; limitExceeded means more routes exist in the areaonebusaway_search_routes toolquery (route name or number) required; maxCount up to 100, default 10shortName, longName, agencyId, and GTFS type; limitExceeded means more routes matched than maxCountendpoint_unavailable on instances whose route-search endpoint returns 404, Puget Sound among them; use onebusaway_find_routes or onebusaway_list_routes_for_agency insteadonebusaway_get_route toolrouteId; returns shortName, longName, description, agency, GTFS type, color, and schedule urlroute_not_found, with recovery via onebusaway_find_routes or onebusaway_search_routesonebusaway_list_routes_for_agency toolagencyId required; unknown agencies fail as agency_not_foundshortName, longName, GTFS type, color, and url; limitExceeded flags an upstream-capped list with no paginationonebusaway_get_arrivals toolstopId 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_foundpredicted (false = schedule-only), scheduleDeviation in seconds (positive = late, meaningful only when predicted), predictedArrivalTime, vehiclePosition, stopsAway, and tripIdsituations[]: those on the stop itself plus those linked from each arrival's situationIds, each onceonebusaway_get_stop_context toolonebusaway_get_arrivals, and the same stop_not_found / rate_limited failures; one call issues one upstream requeststop (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 carriesstop is null; a referenced alert missing from the response is left out; either way a notice names the tool to fetch it withonebusaway_get_alert toolsituationId, from onebusaway_get_arrivals (situations[].id or arrivals[].situationIds); unknown IDs fail as situation_not_foundreason code, severity, consequenceMessage, affects (agency, route, stop, or trip scope), consequences with diversion stop IDs, and activeWindowsonebusaway_get_trip tooltripId 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 distanceAlongTripMetersstatus carries phase (e.g. in_progress, layover_before), predicted, position, scheduleDeviation, and nextStop; blockId (null when the trip has none) feeds onebusaway_get_blocktrip_not_found when the trip isn't active for the service date; a completed trip's times come from onebusaway_get_schedule_for_routeonebusaway_get_block toolblockId, from onebusaway_get_trip; unknown IDs fail as block_not_founddistanceAlongBlock, accumulatedSlackTime (layover seconds), and blockStopTimes; activeServiceIds / inactiveServiceIds show which service calendars applyonebusaway_get_vehicles toolagencyId required, unknown agencies fail as agency_not_found; optional routeId is filtered client-side after all of the agency's vehicles are fetchedposition, orientation, phase, scheduleDeviation, tripId, nextStop, and predicted (reporting real-time GPS); limitExceeded flags an upstream-capped list with no paginationonebusaway_get_schedule_for_stop toolstopId 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_foundscheduledDepartureTime and tripIdonebusaway_get_arrivalsonebusaway_get_schedule_for_route toolrouteId required; optional date as a real YYYY-MM-DD calendar date, default (omitted or blank) today; unknown routes fail as route_not_foundtripId, tripHeadsign, serviceId, and its stop sequenceonebusaway_get_arrivals at a stoponebusaway://stop/{stopId} resourceapplication/json, the same shape onebusaway_get_stop returnsstopId comes from onebusaway_find_stops or onebusaway_search_stopsonebusaway://route/{routeId} resourceapplication/json, the same shape onebusaway_get_route returnsrouteId comes from onebusaway_find_routes or onebusaway_search_routesBuilt 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:
onebusaway-sdk with typed error classification (NotFound, RateLimited, ValidationError for an upstream 400, ServiceUnavailable)api.pugetsound.onebusaway.org), where ONEBUSAWAY_API_KEY=TEST works for development; ONEBUSAWAY_BASE_URL points it at any other OneBusAway instance{agencyId}_{localId} (stop 1_75403, route 1_100259); agency IDs are the bare prefix (1 for Metro Transit, 40 for Sound Transit)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 toolAgent-friendly output:
predicted on every arrival, trip, and vehicle separates GPS-tracked data from schedule-only projectionsscheduleDeviation in seconds; arrival, stop-schedule, and update timestamps in Unix milliseconds; trip, route-schedule, and block stop times in GTFS seconds from midnightstopId 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 listingnotice on empty or truncated resultsA 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"
}
}
}
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
# Server listens at http://localhost:3010/mcp
TEST works on the Puget Sound instance for development; for production use or other instances, register at the relevant agency's developer portal.git clone https://github.com/cyanheads/onebusaway-mcp-server.git
cd onebusaway-mcp-server
bun install
cp .env.example .env
# edit .env — set ONEBUSAWAY_API_KEY if needed
| 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.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync
bun run test # Vitest test suite
bun run test:coverage # Test suite with coverage, held to the framework thresholds
bun run lint:mcp # Validate MCP definitions against spec
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.
| 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. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagecreateApp() arrays in src/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
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:
ONEBUSAWAY_API_KEYdefault: TESTOneBusAway API key. TEST works on the Puget Sound instance for development.
ONEBUSAWAY_BASE_URLdefault: https://api.pugetsound.onebusaway.orgBase URL for the OneBusAway instance. Defaults to Puget Sound.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.