wsdot-mcp-server
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., "@wsdot-mcp-servercheck the Snoqualmie Pass conditions"
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.
Public Hosted Server: https://wsdot.caseyjhand.com/mcp
Overview
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.
Tools
Tool | Description |
| Current conditions for all WA mountain passes: status, road condition, traction laws, temperature, elevation. |
| Active highway alerts — incidents, construction, closures — filterable by state route, WSDOT region, and milepost range. |
| Current vs. average travel times for named WA highway corridors (I-5, I-90, SR 520, etc.) with congestion delay. |
| 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. |
| Current vehicle wait times at all WA/Canada land border crossings. |
| Highway camera metadata and image URLs, filterable by state route, region, milepost range, and title words. |
| All WSF ferry terminals with numeric IDs needed for schedule and space lookups, plus coordinates. |
| 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. |
| Departure times for a specific WSF route — today-remaining or full-day future mode — with each sailing's vessel, loading rule, and notes. |
| Real-time AIS positions, speed, heading, ETA, and dock status for all active WSF vessels. |
| Drive-up and reservable vehicle space available at WSF terminals for upcoming sailings. |
| Active WSF service disruptions and bulletins with impacted route IDs. |
Related MCP server: @cyanheads/onebusaway-mcp-server
Capability reference
wsdot_get_mountain_passes tool
No input parameters — returns current conditions for all 16 WA mountain passes (Snoqualmie, Stevens, White, Blewett, Cayuse, and others) in one call
Fields include road condition, weather, temperature, elevation, and up to two directional traction/travel restrictions
Use for "is the pass open?", traction-law checks, or winter driving planning
wsdot_search_alerts tool
Filter by state route — natural forms all work:
"I-90","90","090", or"SR 520"/"520"Filter by WSDOT region name: Northwest, Olympic, Southwest, South Central, North Central, Eastern (case-insensitive; any other value is rejected with
invalid_region)Filter by milepost range to scope to a corridor — an alert matches when its extent overlaps the range, so a closure that spans the boundary is returned. Either bound may be given alone; a start above the end is rejected with
invalid_milepost_rangeOmit all filters to return all current statewide alerts;
stateRouteandregionaccept at most 200 charactersDescriptions are normalized to plain text; a link renders inline as
link text (url)Results ordered by
alertIdand paged (default 20, max 500) — passoffset/limit; a page also ends early at a 24,000-byte response budget, and the notice reports the next offset
wsdot_get_travel_times tool
Covers I-5, I-90, SR 520, SR 99, I-405, SR 167, and others
Filter by route (
"I-5","5","SR 520") to get every corridor measured on it, or by any text to match corridor names ("Everett");routeaccepts at most 200 charactersWhen current time exceeds average, the corridor is congested; the delta is the delay
Reversible express-lane corridors report no travel time while closed in the queried direction — those figures are omitted rather than reported as zero minutes
Results are paged (default 50, max 500) — pass
offset/limit; a page also ends early at a 24,000-byte response budget, and the notice reports the next offset
wsdot_get_toll_rates tool
Covers SR 99 (WSDOT Tunnel), SR 167 HOT Lanes, I-405 Express Lanes, the SR 509 tolled segment, and the SR 520 Bridge
Rates are time-banded and change dynamically based on traffic conditions
Filter to one facility with
stateRoute—"SR 520","520","0520","I-405", and"405"all work, matched against the posted designation, so"SR 405"matches nothing. A route with no tolled facility returns an empty page whose notice names the tolled routes; the filter (at most 200 characters) is applied before paging and echoed inappliedFiltersEach row's
stateRouteis the bare, zero-padded route number the feed carries ("099","405") with no route type; the rendered text resolves the posted designation, so I-405 reads asI-405rather thanSR 405travelDirectionis the feed's code, not the direction of travel: SR 99, SR 509, and SR 520 carry one fixed code per facility although trips run both ways — read direction from the segment's start and endEach entry leads with its readable
startLocationName → endLocationNamesegment; the opaque upstream trip key stays available astripNameResults are paged (default 50, max 500) — pass
offset/limit; a page also ends early at a 24,000-byte response budget, and the notice reports the next offset
wsdot_get_border_waits tool
No input parameters — covers I-5 (Peace Arch, Blaine), SR 543 (Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas)
Each crossing reports a general-purpose lane and a Nexus lane; SR 539 adds a truck lane and SR 543 adds truck and FAST truck lanes — eleven entries in
crossings[], one per lanecrossingNameis a route code (e.g.I5,SR543Trucks);location.descriptionholds the readable nameWait times in minutes;
updateTimeis ISO 8601. A crossing reporting no current data is still returned — onlywaitTimeInMinutesis omitted, and the rendered text readsNot available
wsdot_search_cameras tool
Filter by state route (
"I-90","90","SR 520", or"520"all work), WSDOT region code, milepost range, or words in the camera title;stateRoute,region, andtitleContainsaccept at most 200 charactersCamera road names carry a route-type prefix, so
"SR 26"excludes US 26 and"US 97"excludes US 97A; a bare"26"returns bothRegion codes:
NW,SW,OL,ER,SC,NC,OS(Oregon — the TripCheck cameras around Portland), andWA(airport cameras plus a few ferry-terminal cameras — most ferry-terminal cameras sit inNWandOL). Case-insensitive; any other value is rejected withinvalid_regiontitleContainsmatches the title as WSDOT wrote it — case-insensitive, every word must appear in any order — so"Snoqualmie"returns Snoqualmie Summit and East Snoqualmie Summit but not Hyak. Titles lead with route and milepost, so filter a route withstateRouteEither milepost bound may be given alone; a start above the end is rejected with
invalid_milepost_rangeReturns metadata and image URLs — camera images are copyright WSDOT, not fetched as bytes
Results are ordered by
cameraIdand paged (default 50, max 500) — passoffset/limit; a page also ends early at a 24,000-byte response budget, and the notice reports the next offset
wsdot_get_ferry_terminals tool
No input parameters — returns all 20 WSF ferry terminals; the list rarely changes
Call this first to resolve human-readable names (e.g. "Bainbridge Island", "Seattle", "Kingston") to the numeric IDs required by
wsdot_get_ferry_scheduleandwsdot_get_terminal_spaceEach terminal also carries its abbreviation and latitude/longitude
wsdot_get_ferry_routes tool
Optional
tripDate(ISO 8601YYYY-MM-DD); defaults to todayReturns each route's ID, abbreviation, and description, plus
terminalPairs: the directed departing → arriving terminal pairs (IDs and names) the route serves that day. These are exactly the pairswsdot_get_ferry_scheduleaccepts for that date; a route serving none carries an empty listRoute IDs correspond to
impactedRouteIdsinwsdot_get_ferry_alerts— use this tool to resolve alert route IDs to route namesA date outside the range WSF has published (before today, or past the posted schedule) returns a typed
invalid_dateerror stating WSF's range. A date inside that range with no sailings loaded yet returns an empty list and a noticeThe pairs cost one lookup per route, cached per date until WSF signals a schedule change; if any lookup fails, the whole call fails
wsdot_get_ferry_schedule tool
Requires
departingTerminalIdandarrivingTerminalId, both positive integers — usewsdot_get_ferry_terminalsfirst, or pick a pair fromterminalPairsonwsdot_get_ferry_routesOptional
tripDate(defaults to today) andremainingOnly: true(only future departures for today; ignored for any other date, and the response then reportsremainingOnly: false)Each sailing carries
vesselId(the IDwsdot_get_vessel_locationsreports),loadingRule,vesselHandicapAccessible, andannotationIndexesinto the pair'sannotations, notes such as "No interisland vehicles. Foot passenger and bikes okay." The rendered text lists each sailing's notes under it. WSF does not documentloadingRule: 3 appears on nearly every sailing and 1 only on vehicle-restricted ones, so read the notes for the restriction itselfannotationsand the pair-widesailingNotesarrive from WSF as HTML and are returned as plain text, with links kept aslink text (url)departureTimeandarrivalTimeare ISO 8601 UTC, whiletripDateis the Pacific service day — an evening sailing therefore carries the following UTC calendar date and will not matchtripDate. Convert toAmerica/Los_Angelesbefore quoting a clock timearrivalTimeis populated on some routes and absent on othersNo cancellation status — WSF drops a cancelled sailing from the schedule rather than flagging it, so a listed sailing is not confirmation it will run; check
wsdot_get_ferry_alerts, which reports disruptions at route levelAn invalid or non-through terminal pair returns a typed
invalid_terminal_pairerror rather than an empty schedule; its recovery hint points atterminalPairsonwsdot_get_ferry_routesfor the same dateA date WSF has no schedule for returns
invalid_dateinstead — whether it falls outside WSF's published range or inside it with no routes loaded
wsdot_get_vessel_locations tool
No input parameters — fields include position, speed, heading, ETA, and dock status for every active WSF vessel
Use for "where is the ferry now?" or checking if a specific vessel is in service
Position data may lag 30–60 seconds; many fields are null for vessels not currently operating
Coordinates render at full upstream AIS precision — no rounding, so both response surfaces report the same position
A vessel between assignments reports an empty
opRouteAbbrev, rendered asnone reportedrather than omitted
wsdot_get_terminal_space tool
Filter to a specific terminal by ID (from
wsdot_get_ferry_terminals); omit for all terminalsdriveUpSpaceCountis the key field — zero means the drive-up lane is full. Oversubscribed sailings report a negative count upstream; it is floored to zero so the value never reads as available spacearrivingTerminalIdslists the terminals a sailing serves and chains straight intowsdot_get_ferry_schedule;itineraryLabelis a display string that may name several stops, not a single destinationResults are paged by terminal (default 5, max 20) —
offset/limitselect whole terminals andtotalCountcounts matching terminals, not sailings; every sailing of a returned terminal is included, so page size varies with how many departures each terminal carries
wsdot_get_ferry_alerts tool
No input parameters — active WSF ferry service disruptions, delays, and bulletins
Each alert carries the bulletin's
alertTitle, its one-linealertDescription, and the fullbulletinText— detail such as a replacement sailing appears only in the bodybulletinTextis plain text: upstream authors it as HTML, and a link is rendered inline aslink text (url)Each alert includes
impactedRouteIds— cross-reference withwsdot_get_ferry_routesto map route IDs to namesaffectsAllRoutes: truemarks a fleet-wide alert, which need not enumerate routes — an emptyimpactedRouteIdsthen means every route rather than none
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.
WSDOT-specific:
Dual API integration — WSDOT Traffic API and WSF Ferry API share a single
WSDOT_ACCESS_CODECross-tool linking built into tool descriptions — ferry tools point to
wsdot_get_ferry_terminals/wsdot_get_ferry_routesfor ID resolution before a lookupNormalized response shapes across both APIs — sparse upstream fields surface as optional rather than omitted or defaulted
Stable pagination — the alert and camera feeds return the same set in more than one row order upstream, so results are sorted by ID to keep a given offset reproducible
Agent-friendly output:
Typed failure —
invalid_access_codeandapi_unavailableerrors carry an explicit recovery hint distinguishing configuration faults from transient upstream onesdriveUpSpaceCount: 0and congestion delta fields (delayInMinutes) give agents actionable signal without string parsingPartial data preserved — sparse upstream payloads surface
null/undefinedrather than synthetic defaults (e.g. an omittedwaitTimeInMinutes, an absentarrivalTime)content[]andstructuredContentcarry the same values, not just the same fields — afalseflag, 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 trimmed
Getting started
Public Hosted Instance
A 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"
}
}
}Self-Hosted / Local
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/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
A WSDOT Traveler API access code. Register at wsdot.wa.gov/Traffic/api/ — registration is free.
Installation
Clone the repository:
git clone https://github.com/cyanheads/wsdot-mcp-server.gitNavigate into the directory:
cd wsdot-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set WSDOT_ACCESS_CODEConfiguration
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
Variable | Description | Default |
| Required. WSDOT Traveler API access code. Register at wsdot.wa.gov/Traffic/api/. | — |
| Transport: |
|
| HTTP server port. |
|
| HTTP server hostname. |
|
| HTTP endpoint path. |
|
| Public origin for TLS-terminating reverse-proxy deployments. | — |
| Session handling: |
|
| Authentication: |
|
| Log level ( |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry instrumentation (spans, metrics, completion logs). |
|
See .env.example for the full list of optional overrides.
Running the server
Local development
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:httpRun 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
docker build -t wsdot-mcp-server .
docker run --rm -e WSDOT_ACCESS_CODE=your-access-code -p 3010:3010 wsdot-mcp-serverThe 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.
Project structure
Directory | Purpose |
|
|
| Server-specific environment variable parsing and validation with Zod. |
| Tool definitions ( |
| WSDOT Traffic API service (mountain passes, alerts, travel times, toll rates, border waits, cameras). |
| WSF Ferry API service (terminals, routes, schedule, vessel locations, space, alerts). |
| Unit and integration tests, mirroring the |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools in the
createApp()arrays insrc/index.tsWrap 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 testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Transport for NSW MCP — journey planning, live departures, service alerts
Real-time transit stops, routes, arrivals, vehicle positions, and schedules via OneBusAway APIs.
Live road conditions in 37 US states: incidents, closures, chain controls, cameras, wildfires.
Provide real-time transportation data including bus arrivals, train service alerts, carpark availa…
Related MCP Servers
- AlicenseBqualityDmaintenanceProvides real-time access to BC highway conditions, road closures, weather alerts, and traffic incidents through the Open511-DriveBC API with smart caching.4MIT
- AlicenseNot gradedqualityBmaintenanceEnables querying stops, routes, real-time arrivals, vehicle positions, and schedules from OneBusAway transit APIs via MCP, supporting STDIO or Streamable HTTP.330 npm1Apache 2.0
- AlicenseAqualityAmaintenanceProvides live California road conditions, route planning, and an AI assistant over MCP, enabling natural-language queries about traffic, closures, chain controls, and more.104MIT
- AlicenseNot gradedqualityDmaintenanceEnables querying real-time BART and SF Muni transit data, including departures, trip planning, fares, advisories, routes, alerts, vehicle positions, and schedules, from any MCP-compatible client.1MIT