@cyanheads/wsdot-mcp-server 0.1.13 → 0.1.15

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 (74) hide show
  1. package/AGENTS.md +39 -28
  2. package/CLAUDE.md +39 -28
  3. package/Dockerfile +1 -1
  4. package/LICENSE +1 -1
  5. package/README.md +5 -3
  6. package/changelog/0.1.x/0.1.14.md +29 -0
  7. package/changelog/0.1.x/0.1.15.md +18 -0
  8. package/dist/mcp-server/tools/definitions/get-border-waits.tool.d.ts +6 -0
  9. package/dist/mcp-server/tools/definitions/get-border-waits.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/get-border-waits.tool.js +7 -0
  11. package/dist/mcp-server/tools/definitions/get-border-waits.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.d.ts +6 -0
  13. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js +7 -0
  15. package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.d.ts +6 -0
  17. package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.js +7 -0
  19. package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.d.ts +6 -0
  21. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js +21 -10
  23. package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.d.ts +6 -0
  25. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js +7 -0
  27. package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js.map +1 -1
  28. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.d.ts +6 -0
  29. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js +7 -0
  31. package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.d.ts +6 -0
  33. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js +7 -0
  35. package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.d.ts +6 -0
  37. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.d.ts.map +1 -1
  38. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js +7 -0
  39. package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js.map +1 -1
  40. package/dist/mcp-server/tools/definitions/get-travel-times.tool.d.ts +6 -0
  41. package/dist/mcp-server/tools/definitions/get-travel-times.tool.d.ts.map +1 -1
  42. package/dist/mcp-server/tools/definitions/get-travel-times.tool.js +28 -9
  43. package/dist/mcp-server/tools/definitions/get-travel-times.tool.js.map +1 -1
  44. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.d.ts +6 -0
  45. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.d.ts.map +1 -1
  46. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js +7 -0
  47. package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js.map +1 -1
  48. package/dist/mcp-server/tools/definitions/search-alerts.tool.d.ts +6 -0
  49. package/dist/mcp-server/tools/definitions/search-alerts.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/definitions/search-alerts.tool.js +18 -4
  51. package/dist/mcp-server/tools/definitions/search-alerts.tool.js.map +1 -1
  52. package/dist/mcp-server/tools/definitions/search-cameras.tool.d.ts +6 -0
  53. package/dist/mcp-server/tools/definitions/search-cameras.tool.d.ts.map +1 -1
  54. package/dist/mcp-server/tools/definitions/search-cameras.tool.js +8 -1
  55. package/dist/mcp-server/tools/definitions/search-cameras.tool.js.map +1 -1
  56. package/dist/services/ferry/ferry-service.d.ts.map +1 -1
  57. package/dist/services/ferry/ferry-service.js +11 -26
  58. package/dist/services/ferry/ferry-service.js.map +1 -1
  59. package/dist/services/traffic/route-match.d.ts +18 -0
  60. package/dist/services/traffic/route-match.d.ts.map +1 -0
  61. package/dist/services/traffic/route-match.js +51 -0
  62. package/dist/services/traffic/route-match.js.map +1 -0
  63. package/dist/services/traffic/traffic-service.d.ts +0 -11
  64. package/dist/services/traffic/traffic-service.d.ts.map +1 -1
  65. package/dist/services/traffic/traffic-service.js +61 -84
  66. package/dist/services/traffic/traffic-service.js.map +1 -1
  67. package/dist/services/traffic/types.d.ts +8 -1
  68. package/dist/services/traffic/types.d.ts.map +1 -1
  69. package/dist/services/wsdot-http.d.ts +47 -0
  70. package/dist/services/wsdot-http.d.ts.map +1 -0
  71. package/dist/services/wsdot-http.js +145 -0
  72. package/dist/services/wsdot-http.js.map +1 -0
  73. package/package.json +8 -8
  74. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/wsdot-mcp-server
4
- **Version:** 0.1.13
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.14`
4
+ **Version:** 0.1.15
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -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` / `ctx.sample`** for presence before calling.
19
+ - **Check `ctx.elicit`** for presence before calling.
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
 
@@ -81,6 +81,8 @@ export function getServerConfig() {
81
81
 
82
82
  `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`WSDOT_ACCESS_CODE`) not the path (`accessCode`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
83
83
 
84
+ For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
85
+
84
86
  ---
85
87
 
86
88
  ## Context
@@ -90,13 +92,14 @@ Handlers receive a unified `ctx` object. Key properties:
90
92
  | Property | Description |
91
93
  |:---------|:------------|
92
94
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
93
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
94
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
95
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
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) { ... }` |
97
+ | `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
+ | `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`. |
96
99
  | `ctx.signal` | `AbortSignal` for cancellation. |
97
100
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
98
101
  | `ctx.requestId` | Unique request ID. |
99
- | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
102
+ | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
100
103
 
101
104
  ---
102
105
 
@@ -104,7 +107,7 @@ Handlers receive a unified `ctx` object. Key properties:
104
107
 
105
108
  Handlers throw — the framework catches, classifies, and formats.
106
109
 
107
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire `data.recovery.hint` (mirrored into `content[]` text), pass explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
110
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
108
111
 
109
112
  ```ts
110
113
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -116,7 +119,7 @@ errors: [
116
119
  ],
117
120
  async handler(input, ctx) {
118
121
  const item = await db.find(input.id);
119
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
122
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
120
123
  return item;
121
124
  }
122
125
  ```
@@ -189,7 +192,7 @@ src/
189
192
 
190
193
  ## Skills
191
194
 
192
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
195
+ Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
193
196
 
194
197
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
195
198
 
@@ -209,54 +212,62 @@ Available skills:
209
212
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
210
213
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
211
214
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
212
- | `devcheck` | Lint, format, typecheck, audit |
213
215
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
214
216
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
215
217
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
216
218
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
219
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
217
220
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
218
221
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
222
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
219
223
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
220
- | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
221
224
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
222
225
  | `api-config` | AppConfig, parseConfig, env vars |
223
226
  | `api-context` | Context interface, logger, state, progress |
224
227
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
228
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
229
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
225
230
  | `api-services` | LLM, Speech, Graph services |
226
231
  | `api-testing` | createMockContext, test patterns |
227
232
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
228
233
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
229
234
  | `api-workers` | Cloudflare Workers runtime |
230
235
 
236
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
237
+
231
238
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
232
239
 
233
240
  ---
234
241
 
235
242
  ## Commands
236
243
 
237
- **Runtime:** Scripts use `tsx` — both `npm run <cmd>` and `bun run <cmd>` work. `bun` is slightly faster for script invocation but not required.
244
+ **Runtime:** Scripts use Bun's native TypeScript execution — `bun run <cmd>` is the standard invocation. `npm run <cmd>` also works (npm delegates to bun).
238
245
 
239
246
  | Command | Purpose |
240
247
  |:--------|:--------|
241
- | `npm run build` | Compile TypeScript |
242
- | `npm run rebuild` | Clean + build |
243
- | `npm run clean` | Remove build artifacts |
244
- | `npm run devcheck` | Lint + format + typecheck + security + changelog sync |
248
+ | `bun run build` | Compile TypeScript |
249
+ | `bun run rebuild` | Clean + build |
250
+ | `bun run clean` | Remove build artifacts |
251
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
245
252
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
246
- | `npm run tree` | Generate directory structure doc |
247
- | `npm run format` | Auto-fix formatting |
248
- | `npm test` | Run tests |
249
- | `npm run start:stdio` | Production mode (stdio) |
250
- | `npm run start:http` | Production mode (HTTP) |
251
- | `npm run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
252
- | `npm run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
253
- | `npm run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
253
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
254
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
255
+ | `bun run list-skills` | Print the skill registry |
256
+ | `bun run tree` | Generate directory structure doc |
257
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
258
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
259
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
260
+ | `bun run start:stdio` | Production mode (stdio) |
261
+ | `bun run start:http` | Production mode (HTTP) |
262
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
263
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
264
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
254
265
 
255
266
  ---
256
267
 
257
268
  ## Bundling
258
269
 
259
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs (`node_modules/**` `skills/`, `.claude/`, `.agents/`, `SKILL.md`) that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
270
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
260
271
 
261
272
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
262
273
 
@@ -274,14 +285,14 @@ Each per-version file opens with YAML frontmatter:
274
285
  ---
275
286
  summary: "One-line headline, ≤350 chars" # required — powers the rollup index
276
287
  breaking: false # optional — true flags breaking changes
277
- security: false # optional — true flags security fixes
288
+ security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
278
289
  ---
279
290
 
280
291
  # 0.1.0 — YYYY-MM-DD
281
292
  ...
282
293
  ```
283
294
 
284
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
295
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
285
296
 
286
297
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
287
298
 
package/CLAUDE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** @cyanheads/wsdot-mcp-server
4
- **Version:** 0.1.13
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.14`
4
+ **Version:** 0.1.15
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -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` / `ctx.sample`** for presence before calling.
19
+ - **Check `ctx.elicit`** for presence before calling.
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
 
@@ -81,6 +81,8 @@ export function getServerConfig() {
81
81
 
82
82
  `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`WSDOT_ACCESS_CODE`) not the path (`accessCode`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
83
83
 
84
+ For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
85
+
84
86
  ---
85
87
 
86
88
  ## Context
@@ -90,13 +92,14 @@ Handlers receive a unified `ctx` object. Key properties:
90
92
  | Property | Description |
91
93
  |:---------|:------------|
92
94
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
93
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
94
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
95
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
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) { ... }` |
97
+ | `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
+ | `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`. |
96
99
  | `ctx.signal` | `AbortSignal` for cancellation. |
97
100
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
98
101
  | `ctx.requestId` | Unique request ID. |
99
- | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
102
+ | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
100
103
 
101
104
  ---
102
105
 
@@ -104,7 +107,7 @@ Handlers receive a unified `ctx` object. Key properties:
104
107
 
105
108
  Handlers throw — the framework catches, classifies, and formats.
106
109
 
107
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire `data.recovery.hint` (mirrored into `content[]` text), pass explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
110
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
108
111
 
109
112
  ```ts
110
113
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -116,7 +119,7 @@ errors: [
116
119
  ],
117
120
  async handler(input, ctx) {
118
121
  const item = await db.find(input.id);
119
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
122
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
120
123
  return item;
121
124
  }
122
125
  ```
@@ -189,7 +192,7 @@ src/
189
192
 
190
193
  ## Skills
191
194
 
192
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
195
+ Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
193
196
 
194
197
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
195
198
 
@@ -209,54 +212,62 @@ Available skills:
209
212
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
210
213
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
211
214
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
212
- | `devcheck` | Lint, format, typecheck, audit |
213
215
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
214
216
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
215
217
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
216
218
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
219
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
217
220
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
218
221
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
222
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
219
223
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
220
- | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
221
224
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
222
225
  | `api-config` | AppConfig, parseConfig, env vars |
223
226
  | `api-context` | Context interface, logger, state, progress |
224
227
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
228
+ | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
229
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
225
230
  | `api-services` | LLM, Speech, Graph services |
226
231
  | `api-testing` | createMockContext, test patterns |
227
232
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
228
233
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
229
234
  | `api-workers` | Cloudflare Workers runtime |
230
235
 
236
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
237
+
231
238
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
232
239
 
233
240
  ---
234
241
 
235
242
  ## Commands
236
243
 
237
- **Runtime:** Scripts use `tsx` — both `npm run <cmd>` and `bun run <cmd>` work. `bun` is slightly faster for script invocation but not required.
244
+ **Runtime:** Scripts use Bun's native TypeScript execution — `bun run <cmd>` is the standard invocation. `npm run <cmd>` also works (npm delegates to bun).
238
245
 
239
246
  | Command | Purpose |
240
247
  |:--------|:--------|
241
- | `npm run build` | Compile TypeScript |
242
- | `npm run rebuild` | Clean + build |
243
- | `npm run clean` | Remove build artifacts |
244
- | `npm run devcheck` | Lint + format + typecheck + security + changelog sync |
248
+ | `bun run build` | Compile TypeScript |
249
+ | `bun run rebuild` | Clean + build |
250
+ | `bun run clean` | Remove build artifacts |
251
+ | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
245
252
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
246
- | `npm run tree` | Generate directory structure doc |
247
- | `npm run format` | Auto-fix formatting |
248
- | `npm test` | Run tests |
249
- | `npm run start:stdio` | Production mode (stdio) |
250
- | `npm run start:http` | Production mode (HTTP) |
251
- | `npm run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
252
- | `npm run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
253
- | `npm run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
253
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
254
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
255
+ | `bun run list-skills` | Print the skill registry |
256
+ | `bun run tree` | Generate directory structure doc |
257
+ | `bun run format` | Auto-fix formatting (safe fixes only) |
258
+ | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
259
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
260
+ | `bun run start:stdio` | Production mode (stdio) |
261
+ | `bun run start:http` | Production mode (HTTP) |
262
+ | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
263
+ | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
264
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
254
265
 
255
266
  ---
256
267
 
257
268
  ## Bundling
258
269
 
259
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs (`node_modules/**` `skills/`, `.claude/`, `.agents/`, `SKILL.md`) that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
270
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
260
271
 
261
272
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
262
273
 
@@ -274,14 +285,14 @@ Each per-version file opens with YAML frontmatter:
274
285
  ---
275
286
  summary: "One-line headline, ≤350 chars" # required — powers the rollup index
276
287
  breaking: false # optional — true flags breaking changes
277
- security: false # optional — true flags security fixes
288
+ security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
278
289
  ---
279
290
 
280
291
  # 0.1.0 — YYYY-MM-DD
281
292
  ...
282
293
  ```
283
294
 
284
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
295
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
285
296
 
286
297
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
287
298
 
package/Dockerfile CHANGED
@@ -58,7 +58,7 @@ RUN bun install --production --frozen-lockfile --ignore-scripts
58
58
  # with: docker build --build-arg OTEL_ENABLED=true
59
59
  ARG OTEL_ENABLED=true
60
60
  RUN if [ "$OTEL_ENABLED" = "true" ]; then \
61
- bun add @hono/otel \
61
+ bun add --omit=dev --ignore-scripts @hono/otel \
62
62
  @opentelemetry/instrumentation-http \
63
63
  @opentelemetry/exporter-metrics-otlp-http \
64
64
  @opentelemetry/exporter-trace-otlp-http \
package/LICENSE CHANGED
@@ -186,7 +186,7 @@ Apache License
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2025 Casey Hand @cyanheads
189
+ Copyright 2026 Casey Hand @cyanheads
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
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.1.13-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-^6.0.3-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.1.15-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/)
11
11
 
12
12
  </div>
13
13
 
@@ -58,7 +58,7 @@ Active WA highway alerts — incidents, construction, closures, restrictions.
58
58
 
59
59
  - Filter by state route — natural forms all work: `"I-90"`, `"90"`, `"090"`, or `"SR 520"` / `"520"`
60
60
  - Filter by WSDOT region: Northwest, Olympic, Southwest, South Central, North Central, Eastern
61
- - Filter by milepost range to scope to a corridor
61
+ - 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
62
62
  - Omit all filters to return all current statewide alerts
63
63
 
64
64
  ---
@@ -68,8 +68,9 @@ Active WA highway alerts — incidents, construction, closures, restrictions.
68
68
  Current vs. average travel times for named WA highway corridors.
69
69
 
70
70
  - Covers I-5, I-90, SR 520, SR 99, I-405, SR 167, and others
71
- - Filter by partial route name (e.g. `"I-5"`, `"SR 520"`) to narrow results
71
+ - Filter by route (`"I-5"`, `"5"`, `"SR 520"`) to get every corridor measured on it, or by any text to match corridor names (`"Everett"`)
72
72
  - When current time exceeds average, the corridor is congested; the delta is the delay
73
+ - Reversible express-lane corridors report no travel time while closed in the queried direction — those figures are omitted rather than reported as zero minutes
73
74
 
74
75
  ---
75
76
 
@@ -97,6 +98,7 @@ Current vehicle wait times at WA/Canada land border crossings.
97
98
  WSDOT highway camera metadata and image URLs.
98
99
 
99
100
  - Filter by state route (`"I-90"`, `"90"`, `"SR 520"`, or `"520"` all work), WSDOT region, or milepost range
101
+ - 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
100
102
  - Returns metadata and image URLs — camera images are copyright WSDOT, not fetched as bytes
101
103
  - Results are paged (default 50, max 500) — pass `offset`/`limit` to page through the full statewide set; the notice reports the next offset
102
104
 
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "WSDOT access code no longer leaks into error payloads (#27); a bad access code now surfaces as its own non-retryable `invalid_access_code` reason instead of a generic `api_unavailable` or bare HTTP 400 (#28); mcp-ts-core ^0.11.0 maintenance."
3
+ breaking: false
4
+ security: true
5
+ ---
6
+
7
+ # 0.1.14 — 2026-07-30
8
+
9
+ ## Changed
10
+
11
+ - **`invalid_access_code` reason** — all 12 tools declare a second error reason alongside `api_unavailable`: `ConfigurationError`, non-retryable, pointing at `https://wsdot.wa.gov/traffic/api/` to register a code. A 401/403, a 4xx whose body names the access code, or an HTML response below HTTP 500 (`Content-Type: text/html` or a body opening as an HTML document) now classifies as `invalid_access_code`; a 5xx HTML page stays `api_unavailable` and retryable, since that's an outage page, not a credential fault ([#28](https://github.com/cyanheads/wsdot-mcp-server/issues/28)).
12
+ - **Body read before status check** — both services read the response body before throwing on a non-2xx status, so the branches that explain a bad access code are reachable at HTTP 400 instead of being short-circuited. WSDOT Traffic's `text/html` + `Bad Request` and WSF's `{"Message": "...Access Code..."}` now both reach the caller, quoted in the error message and carried in `data.body` ([#28](https://github.com/cyanheads/wsdot-mcp-server/issues/28)).
13
+ - **`wsdot_get_ferry_schedule` terminal-pair mapping** — now keys off `data.status` instead of a message regex, and excludes `invalid_access_code`, so a credential rejection is no longer misreported as `invalid_terminal_pair` ([#28](https://github.com/cyanheads/wsdot-mcp-server/issues/28)).
14
+ - Shared upstream-HTTP handling for both services extracted to `src/services/wsdot-http.ts` — redaction, network/timeout classification, and the `invalid_access_code` / `api_unavailable` split now live in one place instead of being duplicated across `TrafficApiService` and `FerryApiService`.
15
+
16
+ ## Security
17
+
18
+ - **Access code no longer reaches error payloads.** `TrafficApiService.fetchJson` and `FerryApiService.fetchJson` now compute a redacted `endpoint` (query string stripped) for every thrown error's `data.url`, covering the non-2xx, HTML, and WSF `{"Message"}` validation throws. Network, timeout, and abort failures are also caught and re-thrown as classified `McpError`s carrying only the redacted endpoint — Bun and Node otherwise hang the full requested URL off a raw fetch rejection as `error.path`. Upstream error bodies are scrubbed of any `AccessCode=…` / `apiaccesscode=…` assignment (scrub-then-truncate, so a cut can never leave a partial credential) before being carried in `data.body` ([#27](https://github.com/cyanheads/wsdot-mcp-server/issues/27)).
19
+ - `tests/security/security.test.ts`'s `SECRET_PATTERNS` previously conflated the credential's name with its value, flagging any prose naming `WSDOT_ACCESS_CODE`; it now matches the fake code and `access_code=<value>` assignments instead, and gained coverage for the error path (the file's header claimed error messages were covered, but only success-path tool output was).
20
+
21
+ ## Dependencies
22
+
23
+ - `@cyanheads/mcp-ts-core` ^0.10.14 → ^0.11.0
24
+ - `typescript` ^6.0.3 → ^7.0.2
25
+ - `@biomejs/biome` ^2.5.0 → ^2.5.5
26
+ - `@types/node` ^26.1.1 → ^26.1.2
27
+ - `ignore` ^7.0.5 → ^7.0.6
28
+ - `tsc-alias` ^1.8.17 → ^1.9.1
29
+ - `vitest` ^4.1.9 → ^4.1.10
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Route filters compare route-type prefix and lettered suffix, so `SR 26` no longer matches US 26 and `US 97` excludes US 97A (#30); travel-times route filtering matches corridor road names, not just names (#26); the CurrentTime=0 sentinel is dropped instead of a zero-minute trip (#29); milepost filtering tests an alert extent for overlap (#35)."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.15 — 2026-07-30
8
+
9
+ ## Changed
10
+
11
+ - **`routeMatches()` replaces `normalizeRoute()`** (`src/services/traffic/route-match.ts`) — parses a route designation into an optional route-type prefix (`i`, `sr`, `us`, `ore`) and a number that retains any lettered suffix. Two designations match when the numbers are equal and, only when both sides carry a prefix, the prefixes are equal too; a suffix letter is part of the number, so `US 97` and `US 97A` stay distinct. `wsdot_search_cameras` and `wsdot_search_alerts` both switch to it. Live-feed effect: `"SR 26"` drops from 21 results (SR 26 + US 26) to 17; `"US 97"` drops from 16 (US 97 + US 97A) to 15; a bare `"26"` still returns both routes ([#30](https://github.com/cyanheads/wsdot-mcp-server/issues/30)).
12
+ - **`wsdot_get_travel_times` route filter** now matches two ways: a route designation against `startPoint.roadName` / `endPoint.roadName` via `routeMatches()`, and a case-insensitive substring against the corridor name (unchanged). Most corridor names are endpoint pairs ("Seattle-Everett") with no route in them, so the route-only match was silently dropping most corridors. Live feed, 163 corridors: `"I-5"` 24 → 118, `"I-405"` 3 → 54, `"SR 520"` 0 → 12 ([#26](https://github.com/cyanheads/wsdot-mcp-server/issues/26)).
13
+ - **`wsdot_search_alerts` milepost range** now tests overlap against an alert's full extent (`startRoadwayLocation.milePost` and `endRoadwayLocation.milePost`) instead of the start point alone, so a closure spanning MP 10–30 is returned for a query at MP 20. The extent is taken as `Math.min`/`Math.max` of the two mileposts, so a decreasing-direction record whose start is numerically greater than its end still matches; either bound may be given alone, and an alert reporting no milepost is always kept ([#35](https://github.com/cyanheads/wsdot-mcp-server/issues/35)).
14
+ - **Unpopulated `RoadwayLocation` zeros dropped.** WSDOT fills the unset end location of a point alert with `MilePost`, `Latitude`, and `Longitude` all `0` — 77 of 178 alerts on the current feed. `normalizeRoadwayLocation()` (`src/services/traffic/traffic-service.ts`) drops that zero triple while keeping the road name and direction the record does carry, so the milepost overlap fix above doesn't read a point alert as stretching from MP 0.
15
+
16
+ ## Fixed
17
+
18
+ - **`CurrentTime`/`AverageTime` of `0`** on a `wsdot_get_travel_times` corridor with a nonzero `Distance` is now dropped during normalization (`isMeasuredDuration()`) instead of surfaced as a measured trip — WSDOT emits `0` for a reversible express lane not open in the queried direction. The guard is keyed on the value, not the corridor name, since the opposite-direction lanes report real times under the same naming (e.g. `Seattle-Everett EL` at 55 min while `Everett-Seattle EL` reports `0`). `delayInMinutes` is derived only when both times are present, so it drops out too; `format()` now renders `**Current:** Not available — WSDOT reports no measurement for this corridor` instead of `0 min`. A `0` on a corridor with no reported distance is kept, since nothing contradicts it ([#29](https://github.com/cyanheads/wsdot-mcp-server/issues/29)).
@@ -24,6 +24,12 @@ export declare const getBorderWaits: import("@cyanheads/mcp-ts-core").ToolDefini
24
24
  readonly when: "WSDOT Traffic API is unreachable or returns a non-2xx response after retries.";
25
25
  readonly retryable: true;
26
26
  readonly recovery: "Retry in 30 seconds. If the issue persists, check wsdot.wa.gov for service status.";
27
+ }, {
28
+ readonly reason: "invalid_access_code";
29
+ readonly code: JsonRpcErrorCode.ConfigurationError;
30
+ readonly when: "WSDOT rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.";
31
+ readonly retryable: false;
32
+ readonly recovery: "Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.";
27
33
  }], {
28
34
  readonly totalCount: z.ZodNumber;
29
35
  readonly notice: z.ZodOptional<z.ZodString>;
@@ -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;;;;;;;;;;;;;;;;;;;;;;;EAsHzB,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;AAGjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8HzB,CAAC"}
@@ -68,6 +68,13 @@ export const getBorderWaits = tool('wsdot_get_border_waits', {
68
68
  retryable: true,
69
69
  recovery: 'Retry in 30 seconds. If the issue persists, check wsdot.wa.gov for service status.',
70
70
  },
71
+ {
72
+ reason: 'invalid_access_code',
73
+ code: JsonRpcErrorCode.ConfigurationError,
74
+ when: 'WSDOT rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.',
75
+ retryable: false,
76
+ recovery: 'Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.',
77
+ },
71
78
  ],
72
79
  async handler(_input, ctx) {
73
80
  const crossings = await getTrafficApiService().getBorderCrossings(ctx);
@@ -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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"}
@@ -17,6 +17,12 @@ export declare const getFerryAlerts: import("@cyanheads/mcp-ts-core").ToolDefini
17
17
  readonly when: "WSF Ferry API is unreachable or returns a non-2xx response after retries.";
18
18
  readonly retryable: true;
19
19
  readonly recovery: "Retry in 30 seconds. If the issue persists, check wsdot.wa.gov/ferries for service status.";
20
+ }, {
21
+ readonly reason: "invalid_access_code";
22
+ readonly code: JsonRpcErrorCode.ConfigurationError;
23
+ readonly when: "WSF rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.";
24
+ readonly retryable: false;
25
+ readonly recovery: "Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.";
20
26
  }], {
21
27
  readonly totalCount: z.ZodNumber;
22
28
  readonly notice: z.ZodOptional<z.ZodString>;
@@ -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;;;;;;;;;;;;;;;;EAkFzB,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;;;;;;;;;;;;;;;;;;;;;;EA0FzB,CAAC"}
@@ -46,6 +46,13 @@ export const getFerryAlerts = tool('wsdot_get_ferry_alerts', {
46
46
  retryable: true,
47
47
  recovery: 'Retry in 30 seconds. If the issue persists, check wsdot.wa.gov/ferries for service status.',
48
48
  },
49
+ {
50
+ reason: 'invalid_access_code',
51
+ code: JsonRpcErrorCode.ConfigurationError,
52
+ when: 'WSF rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.',
53
+ retryable: false,
54
+ recovery: 'Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.',
55
+ },
49
56
  ],
50
57
  async handler(_input, ctx) {
51
58
  const alerts = await getFerryApiService().getAlerts(ctx);
@@ -1 +1 @@
1
- {"version":3,"file":"get-ferry-alerts.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-alerts.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"get-ferry-alerts.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-alerts.tool.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,12 @@ export declare const getFerryRoutes: import("@cyanheads/mcp-ts-core").ToolDefini
18
18
  readonly when: "WSF Ferry API is unreachable or returns a non-2xx response after retries.";
19
19
  readonly retryable: true;
20
20
  readonly recovery: "Retry in 30 seconds. If the issue persists, check wsdot.wa.gov/ferries for service status.";
21
+ }, {
22
+ readonly reason: "invalid_access_code";
23
+ readonly code: JsonRpcErrorCode.ConfigurationError;
24
+ readonly when: "WSF rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.";
25
+ readonly retryable: false;
26
+ readonly recovery: "Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.";
21
27
  }, {
22
28
  readonly reason: "invalid_date";
23
29
  readonly code: JsonRpcErrorCode.ValidationError;
@@ -1 +1 @@
1
- {"version":3,"file":"get-ferry-routes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-routes.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;;;;;;;;;;;;;;;;;;;;;;;EA8GzB,CAAC"}
1
+ {"version":3,"file":"get-ferry-routes.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ferry-routes.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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsHzB,CAAC"}