WA highway conditions, ferry schedules, vessel locations, toll rates, and border waits via MCP.
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. |
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, and milepost range. |
wsdot_get_ferry_terminals | All WSF ferry terminals with numeric IDs needed for schedule and space lookups. |
wsdot_get_ferry_routes | WSF routes operating on a given date — route ID, abbreviation, and description for each, for route discovery and ferry-alert cross-reference. |
wsdot_get_ferry_schedule | Departure times for a specific WSF route — today-remaining or full-day future mode. |
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 tool"I-90", "90", "090", or "SR 520" / "520"link text (url)alertId and paged (default 50, max 500) — pass offset/limit; the notice reports the next offsetwsdot_get_travel_times tool"I-5", "5", "SR 520") to get every corridor measured on it, or by any text to match corridor names ("Everett")offset/limit; the notice reports the next offsetwsdot_get_toll_rates toolstateRoute is a bare, zero-padded route number ("099", "405") with no route type; the rendered text resolves the posted designation, so I-405 reads as I-405 rather than SR 405startLocationName → endLocationName segment; the opaque upstream trip key stays available as tripNameoffset/limit; the notice reports the next offsetwsdot_get_border_waits toolcrossings[], one per lanecrossingName is a route code (e.g. I5, SR543Trucks); location.description holds the readable nameupdateTime is ISO 8601. A crossing reporting no current data is still returned — only waitTimeInMinutes is omitted, and the rendered text reads Not availablewsdot_search_cameras tool"I-90", "90", "SR 520", or "520" all work), WSDOT region, or milepost range"SR 26" excludes US 26 and "US 97" excludes US 97A; a bare "26" returns bothcameraId and paged (default 50, max 500) — pass offset/limit; the notice reports the next offsetwsdot_get_ferry_terminals toolwsdot_get_ferry_schedule and wsdot_get_terminal_spacewsdot_get_ferry_routes tooltripDate (ISO 8601 YYYY-MM-DD); defaults to todayimpactedRouteIds in wsdot_get_ferry_alerts — use this tool to resolve alert route IDs to route nameswsdot_get_ferry_terminalswsdot_get_ferry_schedule tooldepartingTerminalId and arrivingTerminalId — use wsdot_get_ferry_terminals firsttripDate (defaults to today) and remainingOnly: true (only future departures for today; ignored for future dates)departureTime and arrivalTime are ISO 8601 UTC, while tripDate is the Pacific service day — an evening sailing therefore carries the following UTC calendar date and will not match tripDate. Convert to America/Los_Angeles before quoting a clock timearrivalTime is populated on some routes and absent on otherswsdot_get_ferry_alerts, which reports disruptions at route levelinvalid_terminal_pair error rather than an empty schedulewsdot_get_vessel_locations toolopRouteAbbrev, rendered as none reported rather than omittedwsdot_get_terminal_space toolwsdot_get_ferry_terminals); omit for all terminalsdriveUpSpaceCount is 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 spacearrivingTerminalIds lists the terminals a sailing serves and chains straight into wsdot_get_ferry_schedule; itineraryLabel is a display string that may name several stops, not a single destinationoffset/limit select whole terminals and totalCount counts matching terminals, not sailings; every sailing of a returned terminal is included, so page size varies with how many departures each terminal carrieswsdot_get_ferry_alerts toolalertTitle, its one-line alertDescription, and the full bulletinText — detail such as a replacement sailing appears only in the bodybulletinText is plain text: upstream authors it as HTML, and a link is rendered inline as link text (url)impactedRouteIds — cross-reference with wsdot_get_ferry_routes to map route IDs to namesaffectsAllRoutes: true marks a fleet-wide alert, which need not enumerate routes — an empty impactedRouteIds then means every route rather than noneBuilt 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 lookupAgent-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 parsingnull/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 readA 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 |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
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.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/wsdot-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-wsdot-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/wsdot-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/wsdot-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.