@cyanheads/wsdot-mcp-server 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/AGENTS.md +8 -8
  2. package/CLAUDE.md +8 -8
  3. package/Dockerfile +18 -9
  4. package/README.md +17 -8
  5. package/changelog/0.2.x/0.2.3.md +20 -0
  6. package/changelog/0.2.x/0.2.4.md +38 -0
  7. package/changelog/template.md +55 -16
  8. package/dist/index.js +6 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp-server/tools/coordinate-pair.d.ts +18 -0
  11. package/dist/mcp-server/tools/coordinate-pair.d.ts.map +1 -0
  12. package/dist/mcp-server/tools/coordinate-pair.js +22 -0
  13. package/dist/mcp-server/tools/coordinate-pair.js.map +1 -0
  14. package/dist/mcp-server/tools/definitions/get-border-waits.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/get-border-waits.tool.js +11 -7
  16. package/dist/mcp-server/tools/definitions/get-border-waits.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js +11 -7
  19. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js +13 -2
  22. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js +4 -3
  25. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js +5 -4
  28. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js +33 -5
  31. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js +34 -13
  34. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js.map +1 -1
  35. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.d.ts.map +1 -1
  36. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js +11 -7
  37. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js.map +1 -1
  38. package/dist/mcp-server/tools/definitions/search-alerts.tool.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/definitions/search-alerts.tool.js +7 -6
  40. package/dist/mcp-server/tools/definitions/search-alerts.tool.js.map +1 -1
  41. package/dist/mcp-server/tools/definitions/search-cameras.tool.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/definitions/search-cameras.tool.js +15 -7
  43. package/dist/mcp-server/tools/definitions/search-cameras.tool.js.map +1 -1
  44. package/package.json +7 -7
  45. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/wsdot-mcp-server
4
- **Version:** 0.2.2
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
4
+ **Version:** 0.2.4
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
8
  **Zod:** ^4.4.3
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -16,7 +16,7 @@
16
16
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
17
17
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
18
18
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
19
- - **Check `ctx.elicit`** for presence before calling.
19
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
20
20
  - **Secrets in env vars only** — never hardcoded.
21
21
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
22
22
 
@@ -91,13 +91,13 @@ Handlers receive a unified `ctx` object. Key properties:
91
91
 
92
92
  | Property | Description |
93
93
  |:---------|:------------|
94
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
94
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
95
95
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
96
- | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
96
+ | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
97
+ | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |
97
98
  | `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
98
99
  | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
99
100
  | `ctx.signal` | `AbortSignal` for cancellation. |
100
- | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
101
101
  | `ctx.requestId` | Unique request ID. |
102
102
  | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
103
103
 
@@ -223,7 +223,7 @@ Available skills:
223
223
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
224
224
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
225
225
  | `api-config` | AppConfig, parseConfig, env vars |
226
- | `api-context` | Context interface, logger, state, progress |
226
+ | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
227
227
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
228
228
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
229
229
  | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/wsdot-mcp-server
4
- **Version:** 0.2.2
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
4
+ **Version:** 0.2.4
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
8
  **Zod:** ^4.4.3
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -16,7 +16,7 @@
16
16
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
17
17
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
18
18
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
19
- - **Check `ctx.elicit`** for presence before calling.
19
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
20
20
  - **Secrets in env vars only** — never hardcoded.
21
21
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
22
22
 
@@ -91,13 +91,13 @@ Handlers receive a unified `ctx` object. Key properties:
91
91
 
92
92
  | Property | Description |
93
93
  |:---------|:------------|
94
- | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
94
+ | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
95
95
  | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
96
- | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
96
+ | `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
97
+ | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |
97
98
  | `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
98
99
  | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
99
100
  | `ctx.signal` | `AbortSignal` for cancellation. |
100
- | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
101
101
  | `ctx.requestId` | Unique request ID. |
102
102
  | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
103
103
 
@@ -223,7 +223,7 @@ Available skills:
223
223
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
224
224
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
225
225
  | `api-config` | AppConfig, parseConfig, env vars |
226
- | `api-context` | Context interface, logger, state, progress |
226
+ | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
227
227
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
228
228
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
229
229
  | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
package/Dockerfile CHANGED
@@ -4,17 +4,19 @@
4
4
  # This stage installs all dependencies (including dev), builds the TypeScript
5
5
  # source code into JavaScript, and prepares the production assets.
6
6
  # ==============================================================================
7
- FROM oven/bun:1.3.14 AS build
7
+ FROM oven/bun:1.4.0 AS build
8
8
 
9
9
  WORKDIR /usr/src/app
10
10
 
11
11
  # Copy dependency manifests for optimized layer caching
12
12
  COPY package.json bun.lock ./
13
13
 
14
- # Install all dependencies (including dev dependencies for building),
15
- # ignoring lifecycle scripts (native postinstalls unsupported under Bun; the
16
- # build only runs tsc, which needs type declarations, not compiled bindings).
17
- RUN bun install --frozen-lockfile --ignore-scripts
14
+ # Install all dependencies (including dev dependencies for building), ignoring
15
+ # lifecycle scripts (native postinstalls unsupported under Bun; the build only
16
+ # runs tsc, which needs type declarations, not compiled bindings).
17
+ # The BuildKit cache mount persists Bun's global package cache across builds.
18
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
19
+ bun install --frozen-lockfile --ignore-scripts
18
20
 
19
21
  # Copy the rest of the source code
20
22
  COPY . .
@@ -30,7 +32,7 @@ RUN bun run build
30
32
  # application. It uses a slim base image and only includes production
31
33
  # dependencies and build artifacts.
32
34
  # ==============================================================================
33
- FROM oven/bun:1.3.14-slim AS production
35
+ FROM oven/bun:1.4.0-slim AS production
34
36
 
35
37
  WORKDIR /usr/src/app
36
38
 
@@ -51,14 +53,21 @@ COPY package.json bun.lock ./
51
53
 
52
54
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
53
55
  # that are not needed in the final production image.
54
- RUN bun install --production --frozen-lockfile --ignore-scripts
56
+ # `--omit=peer` drops the framework's optional peer tiers (test runner, service
57
+ # SDKs, parsers) that Bun would otherwise auto-install. Anything this server
58
+ # actually imports belongs in its own `dependencies`, so nothing needed at
59
+ # runtime is lost. The OTEL step below carries the same flag — without it, that
60
+ # install re-resolves the graph and pulls every optional peer back in.
61
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
62
+ bun install --production --omit=peer --frozen-lockfile --ignore-scripts
55
63
 
56
64
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
57
65
  # These are not bundled by default to keep the base image lean. Enable at build time
58
66
  # with: docker build --build-arg OTEL_ENABLED=true
59
67
  ARG OTEL_ENABLED=true
60
- RUN if [ "$OTEL_ENABLED" = "true" ]; then \
61
- bun add --omit=dev --ignore-scripts @hono/otel \
68
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
69
+ if [ "$OTEL_ENABLED" = "true" ]; then \
70
+ bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
62
71
  @opentelemetry/instrumentation-http \
63
72
  @opentelemetry/exporter-metrics-otlp-http \
64
73
  @opentelemetry/exporter-trace-otlp-http \
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.2.2-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-^1.29.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/)
10
+ [![Version](https://img.shields.io/badge/Version-0.2.4-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/)
11
11
 
12
12
  </div>
13
13
 
@@ -32,7 +32,7 @@
32
32
  | `wsdot_get_mountain_passes` | Current conditions for all WA mountain passes: status, road condition, traction laws, temperature, elevation. |
33
33
  | `wsdot_search_alerts` | Active highway alerts — incidents, construction, closures — filterable by state route, WSDOT region, and milepost range. |
34
34
  | `wsdot_get_travel_times` | Current vs. average travel times for named WA highway corridors (I-5, I-90, SR 520, etc.) with congestion delay. |
35
- | `wsdot_get_toll_rates` | Dynamic toll rates for WA express lanes and tolled facilities: SR 99, SR 520, I-405, I-90 Two-Way, SR 167 HOT. |
35
+ | `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. |
36
36
  | `wsdot_get_border_waits` | Current vehicle wait times at all WA/Canada land border crossings. |
37
37
  | `wsdot_search_cameras` | Highway camera metadata and image URLs, filterable by state route, region, and milepost range. |
38
38
  | `wsdot_get_ferry_terminals` | All WSF ferry terminals with numeric IDs needed for schedule and space lookups. |
@@ -46,7 +46,7 @@
46
46
 
47
47
  Current road conditions for all WA mountain passes.
48
48
 
49
- - Covers all ~12 passes: Snoqualmie, Stevens, White, Blewett, Cayuse, and others
49
+ - Covers all 16 passes: Snoqualmie, Stevens, White, Blewett, Cayuse, and others
50
50
  - Fields include status (Open/Closed/Caution), road surface, active traction law, temperature, and elevation
51
51
  - Use for "is the pass open?", traction law checks, or winter driving planning
52
52
 
@@ -81,8 +81,10 @@ Current vs. average travel times for named WA highway corridors.
81
81
 
82
82
  Current dynamic toll rates for WA tolled facilities.
83
83
 
84
- - SR 99 (WSDOT Tunnel), SR 520 Bridge, I-405 Express Lanes, I-90 Two-Way Express Lanes, SR 167 HOT Lanes
84
+ - SR 99 (WSDOT Tunnel), SR 167 HOT Lanes, I-405 Express Lanes, the SR 509 tolled segment, and the SR 520 Bridge
85
85
  - Rates are time-banded and change dynamically based on traffic conditions
86
+ - `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`
87
+ - Each entry leads with its readable `startLocationName → endLocationName` segment; the opaque upstream trip key stays available as `tripName`
86
88
  - Results are paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset
87
89
 
88
90
  ---
@@ -91,9 +93,10 @@ Current dynamic toll rates for WA tolled facilities.
91
93
 
92
94
  Current vehicle wait times at WA/Canada land border crossings.
93
95
 
94
- - Covers I-5 (Peace Arch & Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas), including Nexus-lane variants
95
- - `crossingName` is a route code (e.g. `I5`, `SR539`); `location.description` holds the readable name
96
- - Wait times in minutes; a crossing reporting no current data is omitted; `updateTime` is ISO 8601
96
+ - Covers I-5 (Peace Arch, Blaine), SR 543 (Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas)
97
+ - 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
98
+ - `crossingName` is a route code (e.g. `I5`, `SR543Trucks`); `location.description` holds the readable name
99
+ - 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`
97
100
 
98
101
  ---
99
102
 
@@ -112,7 +115,7 @@ WSDOT highway camera metadata and image URLs.
112
115
 
113
116
  All WSF ferry terminals with numeric IDs.
114
117
 
115
- - ~22 terminals; the list rarely changes
118
+ - 20 terminals; the list rarely changes
116
119
  - 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`
117
120
 
118
121
  ---
@@ -134,6 +137,8 @@ Departure times for a specific WSF ferry route.
134
137
  - Requires numeric terminal IDs — use `wsdot_get_ferry_terminals` first
135
138
  - `remainingOnly: true` returns only future departures for today (useful for "next ferry" queries)
136
139
  - For future dates, all sailings for that day are returned
140
+ - `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
141
+ - `arrivalTime` is populated on some routes and absent on others
137
142
  - 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
138
143
 
139
144
  ---
@@ -145,6 +150,8 @@ Real-time AIS positions for all active WSF vessels.
145
150
  - Fields include position, speed, heading, ETA, and dock status
146
151
  - Use for "where is the ferry now?" or checking if a specific vessel is in service
147
152
  - Position data may lag 30–60 seconds; many fields are null for vessels not currently operating
153
+ - Coordinates render at full upstream AIS precision — no rounding, so both response surfaces report the same position
154
+ - A vessel between assignments reports an empty `opRouteAbbrev`, rendered as `none reported` rather than omitted
148
155
 
149
156
  ---
150
157
 
@@ -193,6 +200,7 @@ Agent-friendly output:
193
200
  - Cross-tool linking built into descriptions — ferry tools document which tool to call first for terminal and route ID resolution
194
201
  - `driveUpSpaceCount: 0` and congestion delta fields give agents actionable signal without string parsing
195
202
  - Partial data preserved — sparse upstream payloads surface `null`/`undefined` rather than synthetic defaults
203
+ - `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
196
204
 
197
205
  ## Getting started
198
206
 
@@ -304,6 +312,7 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
304
312
  | `MCP_HTTP_HOST` | HTTP server hostname. | `127.0.0.1` |
305
313
  | `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path. | `/mcp` |
306
314
  | `MCP_PUBLIC_URL` | Public origin for TLS-terminating reverse-proxy deployments. | — |
315
+ | `MCP_SESSION_MODE` | Session handling: `auto`, `stateful`, or `stateless`. The schema default `auto` resolves to stateful; this server sets `stateless` explicitly. | `stateless` |
307
316
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
308
317
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `notice`, `warning`, `error`). | `info` |
309
318
  | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "`content[]` now renders every `false`, empty, and independently-sparse value `structuredContent` already carried (#23); border-wait, toll-rate, and ferry-schedule descriptions corrected to match the live feeds (#33); `tests/` is typechecked (#38) and its injection assertions can now fail (#37)."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.2.3 — 2026-07-30
8
+
9
+ ## Changed
10
+
11
+ - **`content[]` carries every value `structuredContent` carries, not just every field.** `wsdot_get_terminal_space`'s `isCancelled: false` now renders `[not cancelled]` instead of nothing; `wsdot_get_vessel_locations`'s empty `opRouteAbbrev` renders `none reported`; `wsdot_get_ferry_alerts`'s `affectsAllRoutes: false` now renders `not fleet-wide` even when routes are named, which the prior branch never reached. Nine coordinate sites across seven tools now route through a shared `coordinatePair()` (`src/mcp-server/tools/coordinate-pair.ts`) that names whichever side of a lat/long pair is absent instead of dropping a populated one. `wsdot_get_terminal_space`'s drive-up and reservable space counts no longer get suppressed by their display flag, and `maxSpaceCount` now renders on a sailing that reports capacity with no drive-up count. `wsdot_search_cameras` no longer gates its whole location line on `roadName`, and image width/height each render independently ([#23](https://github.com/cyanheads/wsdot-mcp-server/issues/23)).
12
+ - **`wsdot_get_border_waits` description corrected.** Coverage is I-5 (Peace Arch), SR 543 (Pacific Highway — previously misattributed to I-5), SR 539 (Lynden), and SR 9 (Sumas); every crossing carries a general-purpose and a Nexus lane, SR 539 adds a truck lane, and SR 543 adds truck and FAST truck lanes — eleven `crossings[]` entries. "A crossing reporting no current data is omitted" is now accurate: the crossing is always returned, only `waitTimeInMinutes` is dropped ([#33](https://github.com/cyanheads/wsdot-mcp-server/issues/33)).
13
+ - **`wsdot_get_toll_rates` description and rendering corrected.** The feed has no I-90 rows; SR 509 was added to the description. `stateRoute` is documented as a bare, zero-padded route number with no route type. `format()` now resolves the posted designation (`I-405`, not `SR 405`) via a fixed Interstate-number set, and each entry leads with its readable `startLocationName → endLocationName` segment, with the opaque `tripName` still available as a `**Trip:**` field ([#33](https://github.com/cyanheads/wsdot-mcp-server/issues/33)).
14
+ - **`wsdot_get_ferry_schedule` timestamps documented as UTC.** `departureTime`/`arrivalTime` are ISO 8601 UTC while `tripDate` is the Pacific service day, so an evening sailing carries the following UTC calendar date; the description and both fields' `.describe()` now say so and point to converting to `America/Los_Angeles`. `arrivalTime` is documented as populated on some routes and absent on others rather than as a plain "scheduled arrival time" ([#33](https://github.com/cyanheads/wsdot-mcp-server/issues/33)).
15
+ - **`docs/design.md` reconciled against the shipped schemas**, not just the three fields originally reported: `onlyRemainingTimes?` → `remainingOnly?`, the nonexistent `routeName` and `expireDate?` fields removed, `alertTitle?`/`bulletinText?`/`alertType?`/`affectsAllRoutes?` added to the ferry-alerts listing, `offset?`/`limit?` added to every paged tool's input, the three never-built resources removed from the surface list, and stale counts updated (mountain passes ~12 → 16, ferry terminals ~22 → 20, travel-time corridors ~40 → 163, border crossings ~10 → 11, cameras "hundreds" → ~1,700). Enrichment fields are now listed as a surface distinct from tool output ([#36](https://github.com/cyanheads/wsdot-mcp-server/issues/36)).
16
+
17
+ ## Fixed
18
+
19
+ - **The `INJECTION_STRINGS` suite in `tests/security/security.test.ts` can now fail.** It previously mocked every service to `[]`, so there was no rendered output for a payload to appear in. Each case now serves a fixture carrying the payload and asserts it survives byte for byte on both `structuredContent` and `content[]`; a new case covers `${process.env.WSDOT_ACCESS_CODE}` and `{{7*7}}` staying literal rather than evaluating. Seven related cases sharing the same mocked-to-`[]` shape were rewritten the same way ([#37](https://github.com/cyanheads/wsdot-mcp-server/issues/37)).
20
+ - **`tests/` is now typechecked by the same gate as `src/`.** `tsconfig.json` becomes the typecheck config (`rootDir: "."`, `include` covers `src/**/*` and `tests/**/*`, `noEmit: true`); `tsconfig.build.json` keeps `rootDir: "src"`, narrows `include` back to `src/**/*`, and sets `noEmit: false`, so `dist/` is unaffected. Surfaced ~445 pre-existing errors, the largest share from `createMockContext()` not satisfying `HandlerContext` for a tool declaring an `errors[]` contract and from unchecked fixture-array indexing. Fixed with real typing, no suppressions: `tests/helpers/tool-context.ts` adds `toolContext(definition)`, returning a context with `fail`/`recoveryFor` bound to the definition's own declared contract; `tests/helpers/assertions.ts` adds `nth()`, `formattedText()`, and `rejection()` for checked reads in place of casts and `?? ''` fallbacks ([#38](https://github.com/cyanheads/wsdot-mcp-server/issues/38)).
@@ -0,0 +1,38 @@
1
+ ---
2
+ summary: "Adopts mcp-ts-core ^0.12.3 and MCP SDK v2 — tool inputs are strict, so an argument no tool declares is rejected by name instead of dropped; the HTTP endpoint serves protocol revision 2026-07-28, advertised schemas moved to JSON Schema 2020-12, and handler logs now reach the client."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.2.4 — 2026-08-25
8
+
9
+ ## Added
10
+
11
+ - **`tests/tools/input-strictness.test.ts`** pins the strict-input guarantee across all twelve tools: an undeclared key is rejected by name, every declared argument still parses, and the emitted schema advertises `additionalProperties: false` in the 2020-12 dialect.
12
+
13
+ ## Changed
14
+
15
+ - **Tool inputs are strict.** An argument key none of the twelve `wsdot_*` tools declares is now rejected by name, where it was previously dropped and the call ran without it — a misspelled `state_route` on `wsdot_search_alerts` returned every alert in the state and read as a success.
16
+ - **The HTTP endpoint serves MCP protocol revision `2026-07-28`** alongside the 2025 revisions; a 2025-era client negotiates exactly as before.
17
+ - **Advertised schemas moved to JSON Schema 2020-12**, and `outputSchema` now declares the error envelope. A client that rejects draft-07 outright, or that validates `structuredContent` without checking `isError`, no longer fails against these tools.
18
+ - **`ctx.log` output reaches the client** as `notifications/message` at its RFC 5424 level, gated by the client's `logging/setLevel`. Every tool logs a fetch summary, so those lines are now client-visible.
19
+ - **`tools/list` and `server/discover` carry a 24-hour `public` cache hint** for 2026-07-28 clients — the tool surface is fixed at build time and nothing in it is caller-scoped.
20
+ - **HTTP session mode is `stateless` explicitly.** `.env.example` sets `MCP_SESSION_MODE` and the README config table documents it, matching what the published container already sets; the schema default `auto` resolves to stateful, so a source run previously diverged from the container. `/.well-known/mcp.json` and the landing page also report the resolved mode rather than `auto`.
21
+ - **Docker: Bun pins move to `1.4.0`** on both stages, the production install and the OTEL step drop the framework's optional peer tiers (`--omit=peer`), and every install step shares Bun's package cache through a BuildKit cache mount.
22
+ - **`.github/CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`** added, and `SECURITY.md` routes vulnerability reports to `security@caseyjhand.com`.
23
+ - Framework skills, `scripts/`, and `changelog/template.md` re-synced to what mcp-ts-core 0.12.3 ships: `lint-mcp` drops the removed tasks surface, `lint-packaging` tracks the SDK package rename to `@modelcontextprotocol/server`, and `tree` honors directory-only gitignore patterns, so `.claude/` leaves `docs/tree.md`. `tsconfig.json` pins `tsBuildInfoFile` to keep the typecheck cache out of `dist/`, and `typescript` is no longer allowlisted in the `outdated` gate.
24
+
25
+ ## Fixed
26
+
27
+ - **Input-validation failures read as flat `path: message` sentences** instead of a serialized `ZodIssue[]` blob, and a call to an unknown tool rejects with `-32602` rather than resolving an error result.
28
+ - **A failure after the handler returns is recorded as a failed call** — output-schema validation, `format()`, and the enrichment merge run inside the measured region, so they produce an ERROR span and an error counter rather than a success.
29
+ - **Stdin EOF runs the full shutdown path before exiting**, so a stdio server whose client hangs up no longer stays resident.
30
+
31
+ ## Dependencies
32
+
33
+ - `@cyanheads/mcp-ts-core` `^0.11.0` → `^0.12.3` (nine framework releases; `0.12.0` is the MCP SDK v2 adoption)
34
+ - `@biomejs/biome` `^2.5.5` → `^2.5.10`
35
+ - `@types/node` `^26.1.2` → `^26.2.0`
36
+ - `tsc-alias` `^1.9.1` → `^1.9.2`
37
+ - `vitest` `^4.1.10` → `^4.1.11`
38
+ - `packageManager` `bun@1.3.14` → `bun@1.4.0` (`engines.bun` stays `>=1.3.0`)
@@ -4,10 +4,11 @@
4
4
  # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
5
  # with a concrete date.
6
6
 
7
- # Required. One-line GitHub Release-style headline. 350 character cap.
8
- # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
- # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
- # inside the value as a key separator and fails GitHub's strict parser.
7
+ # Required. One-line GitHub Release-style headline. 350 character cap — a
8
+ # ceiling, not a target. Default short and scannable. Don't pad, don't stitch
9
+ # unrelated changes with commas/semicolons into an inventory — pick the
10
+ # headline, like a tag's theme line. Quotes required: unquoted YAML treats
11
+ # `: ` inside the value as a key separator and fails GitHub's strict parser.
11
12
  summary: ""
12
13
 
13
14
  # Set `true` when consumers must change code to upgrade: API removals,
@@ -24,9 +25,10 @@ security: false
24
25
 
25
26
  # Optional free-form notes for maintenance agents processing this release.
26
27
  # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
27
- # downstream servers. Use for adoption instructions that don't fit the
28
- # human-facing sections: new files to create, fields to populate, one-time
29
- # migration steps. Omit the field entirely when there's nothing to say.
28
+ # downstream servers. ADOPTION STEPS ONLY — new files to create, fields to
29
+ # populate, one-time migration steps. Never a second rendering of the body:
30
+ # if a body bullet already says it, name the bullet's symbol instead of
31
+ # re-explaining. Omit the field entirely when there's nothing to say.
30
32
  # agent-notes: |
31
33
  # <instructions for downstream maintenance agents>
32
34
  ---
@@ -41,17 +43,54 @@ security: false
41
43
  each bullet with the symbol or concept name in **bold** so they can skip
42
44
  what's irrelevant and zoom in on what's not.
43
45
 
44
- Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
45
- name the symbol, state what changed, stop. Use a second sentence only when
46
- it carries weight. If a bullet feels long, it is.
47
-
48
- Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
49
- relevant skill), ceremonial framings ("This release introduces…",
50
- backwards-compat paragraphs), file-by-file test enumerations, internal
51
- implementation notes. Prefer code/symbol names over English re-explanations.
46
+ Tone: terse, fact-dense, not verbose. Bullet shape: **symbol** + what
47
+ changed + at most one consumer-facing caveat. One sentence by default, two
48
+ when the second carries weight — a bullet past ~40 words or three sentences
49
+ is wrong. The depth lives one hop away: the linked issue carries the why,
50
+ the commit diff carries the how. The changelog names what changed and what
51
+ a consumer does about it; a reader who wants mechanism opens the link.
52
+
53
+ Model length on THIS guide, never on the previous entry — entries modeled
54
+ on entries compound.
55
+
56
+ Cut (each has shipped as a wall of text; these are the cruft):
57
+ - History/justification narration — how the bug worked, why the old
58
+ behavior was wrong. One short clause at most; the issue carries the story.
59
+ - Design-rationale defense — "chosen over Y because…", "guarding the
60
+ getter is not enough…". That is the author arguing with a reviewer;
61
+ reviewers read the PR, not the changelog.
62
+ - Defensive unchanged-clauses — "X is unchanged", "byte-identical to
63
+ <prev>". Keep one only where its absence would cause a real misread,
64
+ as a short parenthetical.
65
+ - Edge-case inventories — marker lists, not-flagged lists, escape tables.
66
+ Tests and the issue carry those.
67
+ - Mechanism walkthroughs (JSDoc, CLAUDE.md/AGENTS.md, or the relevant
68
+ skill own those), ceremonial framings ("This release introduces…"),
69
+ backwards-compat paragraphs, file-by-file test enumerations. Prefer
70
+ code/symbol names over English re-explanations.
71
+
72
+ Verified ≠ included: the every-claim-verified-from-the-diff rule bounds
73
+ the TRUTH of what you write, never the AMOUNT.
74
+
75
+ Example — same fact, right size:
76
+
77
+ TOO LONG: **`fetchWithTimeout`'s `timeoutMs` bounds the whole exchange**
78
+ (#341). `fetch` resolves once headers arrive and the deadline was
79
+ cleared as the helper returned, so a peer that answered promptly and
80
+ then stalled the stream held the request open indefinitely. A 2xx
81
+ carrying a body now comes back as a passthrough wrapper that disarms
82
+ the deadline when the body closes, errors, or is cancelled; …
83
+ [+90 more words of mechanism and edge cases]
84
+
85
+ RIGHT: **`fetchWithTimeout`'s `timeoutMs` now bounds the whole
86
+ exchange, not just the headers** (#341). A stalled body aborts with
87
+ the same `Timeout` error; the returned `Response` is a wrapper, so
88
+ identity assertions (`toBe(response)`) no longer hold.
52
89
 
53
90
  Narrative intro: skip by default. Add one short sentence only when the
54
- release theme genuinely needs framing the bullets can't carry.
91
+ release theme genuinely needs framing the bullets can't carry. When many
92
+ bullets share one upgrade consequence, state it ONCE — intro line or
93
+ agent-notes — never per bullet.
55
94
 
56
95
  Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
57
96
  Fixed, Security. Include only sections with entries; delete the rest
package/dist/index.js CHANGED
@@ -10,6 +10,12 @@ import { initTrafficApiService } from './services/traffic/traffic-service.js';
10
10
  await createApp({
11
11
  name: 'wsdot-mcp-server',
12
12
  title: 'wsdot-mcp-server',
13
+ // The tool surface is fixed at build time, so a 2026-07-28 client may hold discovery
14
+ // results for a day. Nothing here is tenant- or caller-specific — hence `public`.
15
+ cacheHints: {
16
+ 'tools/list': { ttlMs: 86_400_000, cacheScope: 'public' },
17
+ 'server/discover': { ttlMs: 86_400_000, cacheScope: 'public' },
18
+ },
13
19
  tools: [
14
20
  getMountainPasses,
15
21
  searchAlerts,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,kBAAkB;IACzB,KAAK,EAAE;QACL,iBAAiB;QACjB,YAAY;QACZ,cAAc;QACd,YAAY;QACZ,cAAc;QACd,aAAa;QACb,iBAAiB;QACjB,cAAc;QACd,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;QAChB,cAAc;KACf;IACD,SAAS,EAAE,EAAE;IACb,OAAO,EAAE,EAAE;IACX,YAAY,EACV,2aAA2a;IAC7a,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;IAC/B,KAAK,CAAC,IAAI;QACR,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACjD,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,kBAAkB;IACzB,qFAAqF;IACrF,kFAAkF;IAClF,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QACzD,iBAAiB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;KAC/D;IACD,KAAK,EAAE;QACL,iBAAiB;QACjB,YAAY;QACZ,cAAc;QACd,YAAY;QACZ,cAAc;QACd,aAAa;QACb,iBAAiB;QACjB,cAAc;QACd,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;QAChB,cAAc;KACf;IACD,SAAS,EAAE,EAAE;IACb,OAAO,EAAE,EAAE;IACX,YAAY,EACV,2aAA2a;IAC7a,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;IAC/B,KAAK,CAAC,IAAI;QACR,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACjD,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Renders a latitude/longitude pair for `format()` without dropping a
3
+ * one-sided value.
4
+ * @module mcp-server/tools/coordinate-pair
5
+ */
6
+ /**
7
+ * Formats a coordinate pair for `content[]`.
8
+ *
9
+ * Latitude and longitude are independently optional in both upstream feeds, so a guard
10
+ * requiring both drops a populated side from `content[]` while `structuredContent` still
11
+ * carries it. Naming the absent side keeps the two surfaces equivalent.
12
+ *
13
+ * @param latitude - Latitude, or undefined when the feed omits it.
14
+ * @param longitude - Longitude, or undefined when the feed omits it.
15
+ * @returns The rendered pair, or undefined when neither side is present.
16
+ */
17
+ export declare function coordinatePair(latitude?: number, longitude?: number): string | undefined;
18
+ //# sourceMappingURL=coordinate-pair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate-pair.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/coordinate-pair.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Renders a latitude/longitude pair for `format()` without dropping a
3
+ * one-sided value.
4
+ * @module mcp-server/tools/coordinate-pair
5
+ */
6
+ /**
7
+ * Formats a coordinate pair for `content[]`.
8
+ *
9
+ * Latitude and longitude are independently optional in both upstream feeds, so a guard
10
+ * requiring both drops a populated side from `content[]` while `structuredContent` still
11
+ * carries it. Naming the absent side keeps the two surfaces equivalent.
12
+ *
13
+ * @param latitude - Latitude, or undefined when the feed omits it.
14
+ * @param longitude - Longitude, or undefined when the feed omits it.
15
+ * @returns The rendered pair, or undefined when neither side is present.
16
+ */
17
+ export function coordinatePair(latitude, longitude) {
18
+ if (latitude == null && longitude == null)
19
+ return;
20
+ return `${latitude ?? 'latitude not reported'}, ${longitude ?? 'longitude not reported'}`;
21
+ }
22
+ //# sourceMappingURL=coordinate-pair.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate-pair.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/coordinate-pair.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,QAAiB,EAAE,SAAkB;IAClE,IAAI,QAAQ,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI;QAAE,OAAO;IAClD,OAAO,GAAG,QAAQ,IAAI,uBAAuB,KAAK,SAAS,IAAI,wBAAwB,EAAE,CAAC;AAC5F,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-border-waits.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-border-waits.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8HzB,CAAC"}
1
+ {"version":3,"file":"get-border-waits.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-border-waits.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAIjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgIzB,CAAC"}
@@ -4,13 +4,17 @@
4
4
  */
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
+ import { coordinatePair } from '../../../mcp-server/tools/coordinate-pair.js';
7
8
  import { getTrafficApiService } from '../../../services/traffic/traffic-service.js';
8
9
  export const getBorderWaits = tool('wsdot_get_border_waits', {
9
10
  title: 'Get Border Wait Times',
10
- description: 'Returns current vehicle wait times at WA→Canada land border crossings on I-5 (Peace Arch and ' +
11
- 'Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas), including Nexus-lane variants. ' +
12
- 'crossingName is a route code (e.g. "I5", "I5Nexus", "SR539"); the readable name is in ' +
13
- 'location.description. Wait times are in minutes; a crossing reporting no current data is omitted. ' +
11
+ description: 'Returns current vehicle wait times at WA→Canada land border crossings: I-5 (Peace Arch, Blaine), ' +
12
+ 'SR 543 (Pacific Highway, Blaine), SR 539 (Lynden), and SR 9 (Sumas). Each crossing reports a ' +
13
+ 'general-purpose lane and a Nexus lane; SR 539 adds a truck lane and SR 543 adds truck and ' +
14
+ 'FAST truck lanes — eleven entries in crossings[], one per lane. ' +
15
+ 'crossingName is a route code (e.g. "I5", "I5Nexus", "SR543Trucks"); the readable name is in ' +
16
+ 'location.description. Wait times are in minutes. A crossing reporting no current data is still ' +
17
+ 'returned — only waitTimeInMinutes is omitted. ' +
14
18
  'Use for "how long is the border wait?" questions.',
15
19
  annotations: { readOnlyHint: true },
16
20
  input: z.object({}),
@@ -107,9 +111,9 @@ export const getBorderWaits = tool('wsdot_get_border_waits', {
107
111
  lines.push(`**Direction:** ${c.location.direction}`);
108
112
  if (c.location?.milePost != null)
109
113
  lines.push(`**Milepost:** ${c.location.milePost}`);
110
- if (c.location?.latitude != null && c.location.longitude != null) {
111
- lines.push(`**Coords:** ${c.location.latitude}, ${c.location.longitude}`);
112
- }
114
+ const coords = coordinatePair(c.location?.latitude, c.location?.longitude);
115
+ if (coords)
116
+ lines.push(`**Coords:** ${coords}`);
113
117
  if (c.updateTime)
114
118
  lines.push(`**Updated:** ${c.updateTime}`);
115
119
  lines.push('');
@@ -1 +1 @@
1
- {"version":3,"file":"get-border-waits.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-border-waits.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"get-border-waits.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-border-waits.tool.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-ferry-alerts.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-alerts.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;EAwHzB,CAAC"}
1
+ {"version":3,"file":"get-ferry-alerts.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-alerts.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;EA8HzB,CAAC"}
@@ -93,15 +93,19 @@ export const getFerryAlerts = tool('wsdot_get_ferry_alerts', {
93
93
  lines.push(a.alertDescription);
94
94
  if (a.bulletinText)
95
95
  lines.push(a.bulletinText);
96
- if (a.impactedRouteIds.length > 0) {
97
- lines.push(`**Impacted Route IDs:** ${a.impactedRouteIds.join(', ')} (use wsdot_get_ferry_routes to look up names)`);
98
- }
99
- // An alert with no route IDs is ambiguous on its own — say which of the two it is.
100
- if (a.affectsAllRoutes) {
96
+ lines.push(a.impactedRouteIds.length > 0
97
+ ? `**Impacted Route IDs:** ${a.impactedRouteIds.join(', ')} (use wsdot_get_ferry_routes to look up names)`
98
+ : '**Impacted Route IDs:** none listed');
99
+ // An alert with no route IDs is ambiguous on its own — say which of the two it is. The
100
+ // flag is rendered whenever upstream states it, since "not fleet-wide" is as much a fact
101
+ // as "fleet-wide" and structuredContent carries both.
102
+ if (a.affectsAllRoutes === true) {
101
103
  lines.push('**Impacted Routes:** all routes — this alert affects the whole fleet.');
102
104
  }
103
- else if (a.affectsAllRoutes === false && a.impactedRouteIds.length === 0) {
104
- lines.push('**Impacted Routes:** none listed — this alert names no specific route.');
105
+ else if (a.affectsAllRoutes === false) {
106
+ lines.push(a.impactedRouteIds.length > 0
107
+ ? '**Impacted Routes:** not fleet-wide — only the route IDs above (affectsAllRoutes: false).'
108
+ : '**Impacted Routes:** none listed — this alert names no specific route.');
105
109
  }
106
110
  if (a.publishDate)
107
111
  lines.push(`**Published:** ${a.publishDate}`);