
Wraps the Washington State Department of Transportation's Traveler and Ferry APIs into twelve MCP tools covering highway conditions, ferry operations, tolls, and border crossings. Pull real-time traffic alerts filtered by route and milepost, check mountain pass conditions and traction laws, query dynamic toll rates for express lanes, or grab ferry schedules and vessel positions via AIS. The terminal space tool tells you drive-up availability for upcoming sailings, which is critical for San Juan or Bainbridge runs during peak times. Supports both stdio and streamable HTTP transports, and there's a public hosted instance at wsdot.caseyjhand.com. Reach for this when you need live Washington State transportation data without stitching together multiple state APIs yourself.
Query WA highway conditions, ferry schedules, vessel locations, toll rates, border waits, and alerts via MCP. STDIO or Streamable HTTP.
Washington State transportation data from the WSDOT Traveler API and the WSF Ferry API. Query mountain pass and highway conditions, search alerts and cameras, and track ferry schedules, vessel locations, and terminal space from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
wsdot_get_mountain_passes | Current conditions for all WA mountain passes: status, road condition, traction laws, temperature, elevation. |
wsdot_search_alerts | Active highway alerts — incidents, construction, closures — filterable by state route, WSDOT region, and milepost range. |
wsdot_get_travel_times | Current vs. average travel times for named WA highway corridors (I-5, I-90, SR 520, etc.) with congestion delay. |
wsdot_get_toll_rates | Dynamic toll rates for WA express lanes and tolled facilities — SR 99, SR 167 HOT, I-405 Express, SR 509, SR 520 — filterable by route. |
wsdot_get_border_waits | Current vehicle wait times at all WA/Canada land border crossings. |
wsdot_search_cameras | Highway camera metadata and image URLs, filterable by state route, region, milepost range, and title words. |
wsdot_get_ferry_terminals | All WSF ferry terminals with numeric IDs needed for schedule and space lookups, plus coordinates. |
wsdot_get_ferry_routes | WSF routes operating on a given date — route ID, abbreviation, description, and the terminal pairs each serves, for route discovery, schedule lookups, and ferry-alert cross-reference. |
wsdot_get_ferry_schedule | Departure times for a specific WSF route — today-remaining or full-day future mode — with each sailing's vessel, loading rule, and notes. |
wsdot_get_vessel_locations | Real-time AIS positions, speed, heading, ETA, and dock status for all active WSF vessels. |
wsdot_get_terminal_space | Drive-up and reservable vehicle space available at WSF terminals for upcoming sailings. |
wsdot_get_ferry_alerts | Active WSF service disruptions and bulletins with impacted route IDs. |
wsdot_get_mountain_passes toolwsdot_search_alerts toolstateRoute, region (Northwest, Olympic, Southwest, South Central, North Central, Eastern), and startMilepost / endMilepost — an alert matches when its extent overlaps the range; omit all for every statewide alert. Ordered by alertId, paged (default 20, max 500)invalid_region and a start above the end as invalid_milepost_range, each with a recovery hintwsdot_get_travel_times toolroute — a route ("I-5", "SR 520") returns every corridor measured on it, any other text matches corridor names ("Everett"); paged (default 50, max 500)wsdot_get_toll_rates toolstateRoute matches the posted designation ("I-405", "SR 520"); a route with no tolled facility returns an empty page whose notice names the tolled routes. Paged (default 50, max 500)startLocationName → endLocationName segment. stateRoute is the feed's bare zero-padded number ("099") and travelDirection the feed's code, which SR 99, SR 509, and SR 520 fix per facility — read direction from the segment's endswsdot_get_border_waits toolcrossings[] across I-5 (Peace Arch), SR 543 (Pacific Highway), SR 539 (Lynden), and SR 9 (Sumas): a general-purpose and a Nexus lane at each, plus truck lanes at SR 539 and SR 543crossingName is a route code (I5, SR543Trucks) and location.description the readable name; a lane with no current data omits waitTimeInMinuteswsdot_search_cameras toolstateRoute, region code (NW, SW, OL, ER, SC, NC, OS for the Oregon TripCheck cameras, WA for airport cameras), startMilepost / endMilepost, and titleContains (every word, any order). Ordered by cameraId, paged (default 50, max 500)invalid_region and a reversed range as invalid_milepost_rangewsdot_get_ferry_terminals toolwsdot_get_ferry_schedule and wsdot_get_terminal_space takewsdot_get_ferry_routes tooltripDate (YYYY-MM-DD, default today); a date outside the range WSF has published fails as invalid_date stating that rangeimpactedRouteIds of wsdot_get_ferry_alerts), abbreviation, description, and terminalPairs — the departing → arriving pairs wsdot_get_ferry_schedule accepts that daywsdot_get_ferry_schedule tooldepartingTerminalId and arrivingTerminalId (a pair from terminalPairs on wsdot_get_ferry_routes); optional tripDate (default today) and remainingOnly (future departures only, today only)departureTime / arrivalTime in ISO 8601 UTC (tripDate is the Pacific service day), vesselId, loadingRule, and indexes into the pair's annotations. There is no cancellation flag — WSF drops a cancelled sailing, so check wsdot_get_ferry_alertsinvalid_terminal_pair and a date with none as invalid_date, never as an empty schedulewsdot_get_vessel_locations toolopRouteAbbrev as none reportedwsdot_get_terminal_space tooldepartingTerminalId (from wsdot_get_ferry_terminals), else every terminal; paged by terminal (default 5, max 20), with every sailing of a returned terminal includeddriveUpSpaceCount is the key field — zero means the drive-up lane is full, and an oversubscribed sailing's negative upstream count floors to zero. arrivingTerminalIds chains into wsdot_get_ferry_schedulewsdot_get_ferry_alerts toolalertTitle, a one-line alertDescription, and the full bulletinText (detail such as a replacement sailing appears only in the body)impactedRouteIds map to wsdot_get_ferry_routes; affectsAllRoutes: true marks a fleet-wide alert, whose empty route list then means every routeBuilt 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.
WSDOT-specific:
WSDOT_ACCESS_CODEwsdot_get_ferry_terminals / wsdot_get_ferry_routes for ID resolution before a lookupoffset / limit, and a page also ends early at a 24,000-byte response budget, with the notice naming the next offset"I-90", "90", "090", "SR 520" — and every text filter accepts at most 200 charactersAgent-friendly output:
invalid_access_code and api_unavailable errors carry an explicit recovery hint distinguishing configuration faults from transient upstream onesdriveUpSpaceCount: 0 and congestion delta fields (delayInMinutes) give agents actionable signal without string parsinglink text (url)null/undefined rather than synthetic defaults (e.g. an omitted waitTimeInMinutes, an absent arrivalTime)content[] and structuredContent carry the same values, not just the same fields — a false flag, an empty list, and one populated half of a coordinate pair all render rather than dropping out of the markdown surface that some clients read; a blank or whitespace-only upstream string is absent from both, and a populated one arrives trimmedA public instance is available at https://wsdot.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"wsdot-mcp-server": {
"type": "streamable-http",
"url": "https://wsdot.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file. You'll need a WSDOT Traveler API access code — register at wsdot.wa.gov/Traffic/api/.
{
"mcpServers": {
"wsdot-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/wsdot-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"WSDOT_ACCESS_CODE": "your-access-code"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"wsdot-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/wsdot-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"WSDOT_ACCESS_CODE": "your-access-code"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"wsdot-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "WSDOT_ACCESS_CODE=your-access-code",
"ghcr.io/cyanheads/wsdot-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 WSDOT_ACCESS_CODE=your-access-code bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/wsdot-mcp-server.git
cd wsdot-mcp-server
bun install
cp .env.example .env
# edit .env and set WSDOT_ACCESS_CODE
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
WSDOT_ACCESS_CODE | Required. WSDOT Traveler API access code. Register at wsdot.wa.gov/Traffic/api/. | — |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_HTTP_HOST | HTTP server hostname. | 127.0.0.1 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path. | /mcp |
MCP_PUBLIC_URL | Public origin for TLS-terminating reverse-proxy deployments. | — |
MCP_SESSION_MODE | Session handling: auto, stateful, or stateless. The schema default auto resolves to stateful; this server sets stateless explicitly. | 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 |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result (key-name redaction only). | false |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL; traces go to /v1/traces, metrics to /v1/metrics. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Opt-in OTLP log export; the base endpoint never enables it. | — |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t wsdot-mcp-server .
docker run --rm -e WSDOT_ACCESS_CODE=your-access-code -p 3010:3010 wsdot-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/wsdot-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 all 12 tools and initializes services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) — 6 traffic tools, 6 ferry tools. |
src/services/traffic | WSDOT Traffic API service (mountain passes, alerts, travel times, toll rates, border waits, cameras). |
src/services/ferry | WSF Ferry API service (terminals, routes, schedule, vessel locations, space, alerts). |
tests/ | Unit and integration tests, mirroring the src/ structure. |
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.
WSDOT_ACCESS_CODE*WSDOT Traveler API access code. Register at wsdot.wa.gov/Traffic/api/.
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'.