{
  "markdown": "<div align=\"center\">\n  <h1>@cyanheads/wsdot-mcp-server</h1>\n  <p><b>Query WA highway conditions, ferry schedules, vessel locations, toll rates, border waits, and alerts via MCP. STDIO or Streamable HTTP.</b>\n  <div>12 Tools</div>\n  </p>\n</div>\n\n<div align=\"center\">\n\n[![Version](https://img.shields.io/badge/Version-0.2.5-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/wsdot-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/wsdot-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/wsdot-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0+-blueviolet.svg?style=flat-square)](https://bun.sh/)\n\n</div>\n\n<div align=\"center\">\n\n[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/wsdot-mcp-server/releases/latest/download/wsdot-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=wsdot-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvd3Nkb3QtbWNwLXNlcnZlciJdLCJlbnYiOnsiV1NET1RfQUNDRVNTX0NPREUiOiJ5b3VyLWFjY2Vzcy1jb2RlIn19) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22wsdot-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fwsdot-mcp-server%22%5D%2C%22env%22%3A%7B%22WSDOT_ACCESS_CODE%22%3A%22your-access-code%22%7D%7D)\n\n[![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)\n\n**Public Hosted Server:** [https://wsdot.caseyjhand.com/mcp](https://wsdot.caseyjhand.com/mcp)\n\n</div>\n\n---\n\n## Tools\n\n12 tools split across two domains — traffic (WSDOT Traveler API) and ferries (WSF Ferry API):\n\n| Tool | Description |\n|:---|:---|\n| `wsdot_get_mountain_passes` | Current conditions for all WA mountain passes: status, road condition, traction laws, temperature, elevation. |\n| `wsdot_search_alerts` | Active highway alerts — incidents, construction, closures — filterable by state route, WSDOT region, and milepost range. |\n| `wsdot_get_travel_times` | Current vs. average travel times for named WA highway corridors (I-5, I-90, SR 520, etc.) with congestion delay. |\n| `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. |\n| `wsdot_get_border_waits` | Current vehicle wait times at all WA/Canada land border crossings. |\n| `wsdot_search_cameras` | Highway camera metadata and image URLs, filterable by state route, region, and milepost range. |\n| `wsdot_get_ferry_terminals` | All WSF ferry terminals with numeric IDs needed for schedule and space lookups. |\n| `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. |\n| `wsdot_get_ferry_schedule` | Departure times for a specific WSF route — today-remaining or full-day future mode. |\n| `wsdot_get_vessel_locations` | Real-time AIS positions, speed, heading, ETA, and dock status for all active WSF vessels. |\n| `wsdot_get_terminal_space` | Drive-up and reservable vehicle space available at WSF terminals for upcoming sailings. |\n| `wsdot_get_ferry_alerts` | Active WSF service disruptions and bulletins with impacted route IDs. |\n\n### `wsdot_get_mountain_passes`\n\nCurrent road conditions for all WA mountain passes.\n\n- Covers all 16 passes: Snoqualmie, Stevens, White, Blewett, Cayuse, and others\n- Fields include status (Open/Closed/Caution), road surface, active traction law, temperature, and elevation\n- Use for \"is the pass open?\", traction law checks, or winter driving planning\n\n---\n\n### `wsdot_search_alerts`\n\nActive WA highway alerts — incidents, construction, closures, restrictions.\n\n- Filter by state route — natural forms all work: `\"I-90\"`, `\"90\"`, `\"090\"`, or `\"SR 520\"` / `\"520\"`\n- Filter by WSDOT region: Northwest, Olympic, Southwest, South Central, North Central, Eastern\n- 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\n- Omit all filters to return all current statewide alerts\n- Descriptions are plain text — upstream authors them with markup, and a link is rendered inline as `link text (url)` so the destination survives\n- Results are ordered by `alertId` and paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset. Upstream returns the same alert set in more than one row order, so the ordering is imposed here to keep a given offset reproducible\n\n---\n\n### `wsdot_get_travel_times`\n\nCurrent vs. average travel times for named WA highway corridors.\n\n- Covers I-5, I-90, SR 520, SR 99, I-405, SR 167, and others\n- Filter by route (`\"I-5\"`, `\"5\"`, `\"SR 520\"`) to get every corridor measured on it, or by any text to match corridor names (`\"Everett\"`)\n- When current time exceeds average, the corridor is congested; the delta is the delay\n- Reversible express-lane corridors report no travel time while closed in the queried direction — those figures are omitted rather than reported as zero minutes\n- Results are paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset\n\n---\n\n### `wsdot_get_toll_rates`\n\nCurrent dynamic toll rates for WA tolled facilities.\n\n- SR 99 (WSDOT Tunnel), SR 167 HOT Lanes, I-405 Express Lanes, the SR 509 tolled segment, and the SR 520 Bridge\n- Rates are time-banded and change dynamically based on traffic conditions\n- `stateRoute` 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 405`\n- Each entry leads with its readable `startLocationName → endLocationName` segment; the opaque upstream trip key stays available as `tripName`\n- Results are paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset\n\n---\n\n### `wsdot_get_border_waits`\n\nCurrent vehicle wait times at WA/Canada land border crossings.\n\n- Covers I-5 (Peace Arch, Blaine), SR 543 (Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas)\n- 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 lane\n- `crossingName` is a route code (e.g. `I5`, `SR543Trucks`); `location.description` holds the readable name\n- Wait times in minutes; `updateTime` is ISO 8601. A crossing reporting no current data is still returned — only `waitTimeInMinutes` is omitted, and the rendered text reads `Not available`\n\n---\n\n### `wsdot_search_cameras`\n\nWSDOT highway camera metadata and image URLs.\n\n- Filter by state route (`\"I-90\"`, `\"90\"`, `\"SR 520\"`, or `\"520\"` all work), WSDOT region, or milepost range\n- Camera road names carry a route-type prefix, so `\"SR 26\"` excludes US 26 and `\"US 97\"` excludes US 97A; a bare `\"26\"` returns both\n- Returns metadata and image URLs — camera images are copyright WSDOT, not fetched as bytes\n- Results are ordered by `cameraId` and paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset. Upstream returns the same camera set in more than one row order, so the ordering is imposed here to keep a given offset reproducible\n\n---\n\n### `wsdot_get_ferry_terminals`\n\nAll WSF ferry terminals with numeric IDs.\n\n- 20 terminals; the list rarely changes\n- Call this first to resolve human-readable names (e.g. \"Bainbridge Island\", \"Seattle\", \"Kingston\") to the numeric IDs required by `wsdot_get_ferry_schedule` and `wsdot_get_terminal_space`\n\n---\n\n### `wsdot_get_ferry_routes`\n\nWSF ferry routes operating on a given date.\n\n- Returns each route's ID, abbreviation, and description\n- Route IDs correspond to `impactedRouteIds` in `wsdot_get_ferry_alerts` — use this tool to resolve alert route IDs to route names\n- Use to discover which routes are running; for the numeric terminal IDs that schedule and space lookups need, call `wsdot_get_ferry_terminals`\n\n---\n\n### `wsdot_get_ferry_schedule`\n\nDeparture times for a specific WSF ferry route.\n\n- Requires numeric terminal IDs — use `wsdot_get_ferry_terminals` first\n- `remainingOnly: true` returns only future departures for today (useful for \"next ferry\" queries)\n- For future dates, all sailings for that day are returned\n- `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 time\n- `arrivalTime` is populated on some routes and absent on others\n- No 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 level\n\n---\n\n### `wsdot_get_vessel_locations`\n\nReal-time AIS positions for all active WSF vessels.\n\n- Fields include position, speed, heading, ETA, and dock status\n- Use for \"where is the ferry now?\" or checking if a specific vessel is in service\n- Position data may lag 30–60 seconds; many fields are null for vessels not currently operating\n- Coordinates render at full upstream AIS precision — no rounding, so both response surfaces report the same position\n- A vessel between assignments reports an empty `opRouteAbbrev`, rendered as `none reported` rather than omitted\n\n---\n\n### `wsdot_get_terminal_space`\n\nReal-time vehicle space availability at WSF terminals for upcoming sailings.\n\n- `driveUpSpaceCount` 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 space\n- `arrivingTerminalIds` 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 destination\n- Filter to a specific terminal by ID (from `wsdot_get_ferry_terminals`)\n- Use for \"will I make the ferry?\" or \"how full is the next sailing?\" queries\n- Results are paged by terminal (default 5, max 20) — `offset`/`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 carries\n\n---\n\n### `wsdot_get_ferry_alerts`\n\nActive WSF ferry service disruptions, delays, and bulletins.\n\n- Each alert carries the bulletin's `alertTitle`, its one-line `alertDescription`, and the full `bulletinText` — detail such as a replacement sailing appears only in the body\n- `bulletinText` is plain text: upstream authors it as HTML, and a link is rendered inline as `link text (url)`\n- Each alert includes `impactedRouteIds` — cross-reference with `wsdot_get_ferry_routes` to map route IDs to names\n- `affectsAllRoutes: true` marks a fleet-wide alert, which need not enumerate routes — an empty `impactedRouteIds` then means every route rather than none\n\n---\n\n## Features\n\nBuilt on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):\n\n- Declarative tool definitions — single file per tool, framework handles registration and validation\n- Unified error handling across all tools\n- Pluggable auth (`none`, `jwt`, `oauth`)\n- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`\n- Structured logging with optional OpenTelemetry tracing\n- STDIO and Streamable HTTP transports\n\nWSDOT-specific:\n\n- Dual API integration — WSDOT Traffic API and WSF Ferry API from a single access code\n- Retry, timeout, and HTML-detection guards on all upstream requests\n- Normalized response shapes across both APIs — sparse upstream fields surfaced as optional rather than omitted\n\nAgent-friendly output:\n\n- Cross-tool linking built into descriptions — ferry tools document which tool to call first for terminal and route ID resolution\n- `driveUpSpaceCount: 0` and congestion delta fields give agents actionable signal without string parsing\n- Partial data preserved — sparse upstream payloads surface `null`/`undefined` rather than synthetic defaults\n- `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\n\n## Getting started\n\nAdd the following to your MCP client configuration file. You'll need a WSDOT Traveler API access code — register at [wsdot.wa.gov/Traffic/api/](https://wsdot.wa.gov/Traffic/api/).\n\n```json\n{\n  \"mcpServers\": {\n    \"wsdot-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"bunx\",\n      \"args\": [\"@cyanheads/wsdot-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\",\n        \"WSDOT_ACCESS_CODE\": \"your-access-code\"\n      }\n    }\n  }\n}\n```\n\nOr with npx (no Bun required):\n\n```json\n{\n  \"mcpServers\": {\n    \"wsdot-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@cyanheads/wsdot-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\",\n        \"WSDOT_ACCESS_CODE\": \"your-access-code\"\n      }\n    }\n  }\n}\n```\n\nOr with Docker:\n\n```json\n{\n  \"mcpServers\": {\n    \"wsdot-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\", \"-i\", \"--rm\",\n        \"-e\", \"MCP_TRANSPORT_TYPE=stdio\",\n        \"-e\", \"WSDOT_ACCESS_CODE=your-access-code\",\n        \"ghcr.io/cyanheads/wsdot-mcp-server:latest\"\n      ]\n    }\n  }\n}\n```\n\nFor Streamable HTTP, set the transport and start the server:\n\n```sh\nMCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 WSDOT_ACCESS_CODE=your-access-code bun run start:http\n# Server listens at http://localhost:3010/mcp\n```\n\n### Prerequisites\n\n- [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).\n- A WSDOT Traveler API access code. Register at [wsdot.wa.gov/Traffic/api/](https://wsdot.wa.gov/Traffic/api/) — registration is free.\n\n### Installation\n\n1. **Clone the repository:**\n\n```sh\ngit clone https://github.com/cyanheads/wsdot-mcp-server.git\n```\n\n2. **Navigate into the directory:**\n\n```sh\ncd wsdot-mcp-server\n```\n\n3. **Install dependencies:**\n\n```sh\nbun install\n```\n\n4. **Configure environment:**\n\n```sh\ncp .env.example .env\n# edit .env and set WSDOT_ACCESS_CODE\n```\n\n## Configuration\n\nAll configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:\n\n| Variable | Description | Default |\n|:---|:---|:---|\n| `WSDOT_ACCESS_CODE` | **Required.** WSDOT Traveler API access code. Register at [wsdot.wa.gov/Traffic/api/](https://wsdot.wa.gov/Traffic/api/). | — |\n| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |\n| `MCP_HTTP_PORT` | HTTP server port. | `3010` |\n| `MCP_HTTP_HOST` | HTTP server hostname. | `127.0.0.1` |\n| `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path. | `/mcp` |\n| `MCP_PUBLIC_URL` | Public origin for TLS-terminating reverse-proxy deployments. | — |\n| `MCP_SESSION_MODE` | Session handling: `auto`, `stateful`, or `stateless`. The schema default `auto` resolves to stateful; this server sets `stateless` explicitly. | `stateless` |\n| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |\n| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `notice`, `warning`, `error`). | `info` |\n| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |\n| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |\n| `OTEL_ENABLED` | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | `false` |\n\nSee [`.env.example`](./.env.example) for the full list of optional overrides.\n\n## Running the server\n\n### Local development\n\n- **Build and run:**\n\n  ```sh\n  # One-time build\n  bun run rebuild\n\n  # Run the built server\n  bun run start:stdio\n  # or\n  bun run start:http\n  ```\n\n- **Run checks and tests:**\n\n  ```sh\n  bun run devcheck   # Lint, format, typecheck, security\n  bun run test       # Vitest test suite\n  bun run lint:mcp   # Validate MCP definitions against spec\n  ```\n\n### Docker\n\n```sh\ndocker build -t wsdot-mcp-server .\ndocker run --rm -e WSDOT_ACCESS_CODE=your-access-code -p 3010:3010 wsdot-mcp-server\n```\n\nThe 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.\n\n## Project structure\n\n| Directory | Purpose |\n|:---|:---|\n| `src/index.ts` | `createApp()` entry point — registers all 12 tools and initializes services. |\n| `src/config` | Server-specific environment variable parsing and validation with Zod. |\n| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`) — 6 traffic tools, 6 ferry tools. |\n| `src/services/traffic` | WSDOT Traffic API service (mountain passes, alerts, travel times, toll rates, border waits, cameras). |\n| `src/services/ferry` | WSF Ferry API service (terminals, routes, schedule, vessel locations, space, alerts). |\n| `tests/` | Unit and integration tests, mirroring the `src/` structure. |\n\n## Development guide\n\nSee [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:\n\n- Handlers throw, framework catches — no `try/catch` in tool logic\n- Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage\n- Register new tools in the `createApp()` arrays in `src/index.ts`\n- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields\n\n## Contributing\n\nIssues and pull requests are welcome. Run checks and tests before submitting:\n\n```sh\nbun run devcheck\nbun run test\n```\n\n## License\n\nApache-2.0 — see [LICENSE](./LICENSE) for details.\n",
  "bytes": 18868,
  "sha": "aa2e52f00339c0f8cb8d6a4ce566a1349f378673991671cb99040bbf601c7a2d",
  "repo_slug": "cyanheads/wsdot-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cyanheads_wsdot_mcp_server_6848d705/readme"
}