onebusaway-mcp-server-22d18f — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited onebusaway-mcp-server-22d18f (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/onebusaway-mcp-server</h1> <p><b>Query stops, routes, real-time arrivals, vehicle positions, and schedules from OneBusAway transit APIs via MCP. STDIO or Streamable HTTP.</b> <div>16 Tools • 2 Resources</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://onebusaway.caseyjhand.com/mcp
</div>
Real-time transit from OneBusAway, set up for Puget Sound (King County Metro, Sound Transit, Pierce Transit, Community Transit, and more) and compatible with any OneBusAway instance. Find stops and routes, track live arrivals and vehicle positions, and pull schedules and service alerts.
| 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 <sub>tool</sub>id, contact info, timezone, and coverageCenter / coverageSpanlimitExceeded flags an upstream-capped list, with no pagination to fetch the restonebusaway_find_stops <sub>tool</sub>lat / 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 <sub>tool</sub>query (stop name fragment or stop code) required; maxCount up to 100, default 10onebusaway_find_stops; limitExceeded means more stops matched than maxCountonebusaway_get_stop <sub>tool</sub>stopId; returns name, code, coordinates, direction, routeIds, and wheelchairBoardingstop_not_found, with recovery via onebusaway_find_stops or onebusaway_search_stopsonebusaway_find_routes <sub>tool</sub>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 numbershortName, longName, agencyId, GTFS type (0=tram … 5=cable_car), color, and schedule url; limitExceeded means more routes exist in the areaonebusaway_search_routes <sub>tool</sub>query (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 <sub>tool</sub>routeId; 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 <sub>tool</sub>agencyId 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 <sub>tool</sub>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_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 <sub>tool</sub>onebusaway_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 <sub>tool</sub>situationId, 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 <sub>tool</sub>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 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 <sub>tool</sub>blockId, from onebusaway_get_trip; unknown IDs fail as block_not_foundtrips, activeServiceIds, and inactiveServiceIds describe the first returned configuration; alternateConfigurations preserves every remaining configuration in order. Service IDs are calendar conditions, not a claim that a configuration operates todaydistanceAlongBlock, accumulatedSlackTime (layover seconds), and blockStopTimes. Optional exact tripId or departureFromSec / departureBeforeSec selects complete trips independently in every configuration, retaining empty configuration slotsselection.total / returned count trip occurrences across configurations; configurationCounts[0] describes the top-level configuration, then one entry per alternativeExample: {"blockId":"<blockId from onebusaway_get_trip>","departureFromSec":86400,"departureBeforeSec":90000} selects trips with any departure from 24:00 inclusive to 25:00 exclusive, retaining their complete stop sequences.
onebusaway_get_vehicles <sub>tool</sub>agencyId 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 paginationtripId prefers the active trip; route and headsign metadata resolve only from that trip. orientation preserves counter-clockwise degrees from east (0=east, 90=north, 180=west, 270=south), not a compass bearingomittedWithoutPosition counts them across the agency, and omittedWithoutPositionOnRoute counts resolved matches when filtering by route. These counts are independent of upstream truncationonebusaway_get_schedule_for_stop <sub>tool</sub>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_foundscheduledDepartureTime and tripIdserviceDateMs preserves the upstream schedule-date marker: query time for an omitted/blank date, instance-local midnight for an explicit date; it is not rounded to a service-day boundaryrouteId, departureFromMs, and departureBeforeMs combine with AND; time bounds are non-negative integer Unix milliseconds, lower inclusive and upper exclusive. Selected results omit empty directions/routes; selection.total / returned count departure rows before/after selectiononebusaway_get_arrivalsExample: {"stopId":"1_75403","routeId":"1_100259","departureFromMs":1792882800000,"departureBeforeMs":1792886400000} selects that route's departures in the given epoch interval.
onebusaway_get_schedule_for_route <sub>tool</sub>routeId 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 sequenceserviceDateMs preserves the upstream route scheduleDate, identifying the schedule's service daytripId or departureFromSec / departureBeforeSec selects whole trips having any departure in the interval. Bounds are non-negative integer GTFS seconds from midnight, including values beyond 86400; lower inclusive, upper exclusive. selection.total / returned count trips before/after selectiononebusaway_get_arrivals at a stopExample: {"routeId":"1_100259","departureFromSec":28800,"departureBeforeSec":32400} selects trips with any departure from 08:00 inclusive to 09:00 exclusive.
For route and block schedules, a nonblank tripId cannot be combined with departure bounds. Blank optional IDs mean omitted; either bound may be omitted, and two supplied bounds must increase. Without selectors, all three tools return their complete mapped response with no selection metadata or default cap. Selection counts describe only that upstream response, not omitted upstream service; a zero match is a successful result with a notice.
Arrivals, stop context, trips, vehicles, schedules, and alerts include a validated IANA timezone; epoch clocks and date headings carry that zone explicitly. Stop tools use the queried stop's agency namespace, vehicles the requested agency, and trip/route schedules the referenced route's agency. Each alert resolves its own agency, route, trip, and stop scopes and uses their zone only when every scope resolves and agrees. Missing, invalid, unresolved, or conflicting zones use UTC, without additional requests. Epoch numbers and GTFS seconds remain unchanged, including schedule times beyond 24:00.
onebusaway://stop/{stopId} <sub>resource</sub>application/json, the same shape onebusaway_get_stop returnsstopId comes from onebusaway_find_stops or onebusaway_search_stopsonebusaway://route/{routeId} <sub>resource</sub>application/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/mcpTEST 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.gitcd onebusaway-mcp-serverbun installcp .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.
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio 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 specdocker build -t onebusaway-mcp-server .
docker run --rm -e ONEBUSAWAY_API_KEY=TEST -p 3010:3010 onebusaway-mcp-serverThe 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 testApache-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:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.