@cyanheads/wsdot-mcp-server 0.2.5 → 0.3.0
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.
- package/AGENTS.md +45 -18
- package/CLAUDE.md +45 -18
- package/README.md +77 -81
- package/changelog/0.2.x/0.2.6.md +29 -0
- package/changelog/0.3.x/0.3.0.md +30 -0
- package/changelog/template.md +9 -26
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-border-waits.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-border-waits.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-border-waits.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/get-border-waits.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js +2 -0
- package/dist/mcp-server/tools/definitions/get-ferry-alerts.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/get-ferry-routes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js +11 -7
- package/dist/mcp-server/tools/definitions/get-ferry-schedule.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/get-ferry-terminals.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/get-mountain-passes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-terminal-space.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-terminal-space.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js +4 -0
- package/dist/mcp-server/tools/definitions/get-terminal-space.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-toll-rates.tool.d.ts +6 -0
- package/dist/mcp-server/tools/definitions/get-toll-rates.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js +126 -80
- package/dist/mcp-server/tools/definitions/get-toll-rates.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-travel-times.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-travel-times.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-travel-times.tool.js +105 -101
- package/dist/mcp-server/tools/definitions/get-travel-times.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.d.ts +2 -0
- package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js +3 -1
- package/dist/mcp-server/tools/definitions/get-vessel-locations.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-alerts.tool.d.ts +12 -0
- package/dist/mcp-server/tools/definitions/search-alerts.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-alerts.tool.js +155 -131
- package/dist/mcp-server/tools/definitions/search-alerts.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-cameras.tool.d.ts +14 -0
- package/dist/mcp-server/tools/definitions/search-cameras.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-cameras.tool.js +121 -68
- package/dist/mcp-server/tools/definitions/search-cameras.tool.js.map +1 -1
- package/dist/mcp-server/tools/page-budget.d.ts +56 -0
- package/dist/mcp-server/tools/page-budget.d.ts.map +1 -0
- package/dist/mcp-server/tools/page-budget.js +76 -0
- package/dist/mcp-server/tools/page-budget.js.map +1 -0
- package/dist/services/ferry/ferry-service.js +3 -3
- package/dist/services/traffic/traffic-service.js +3 -3
- package/package.json +10 -9
- package/server.json +3 -3
|
@@ -4,7 +4,9 @@
|
|
|
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 '
|
|
7
|
+
import { coordinatePair } from '../coordinate-pair.js';
|
|
8
|
+
import { continuationNotice, fitPageToBudget, MAX_FILTER_LENGTH, PAGE_BYTE_BUDGET, renderPage, } from '../page-budget.js';
|
|
9
|
+
import { routeMatches } from '../../../services/traffic/route-match.js';
|
|
8
10
|
import { getTrafficApiService } from '../../../services/traffic/traffic-service.js';
|
|
9
11
|
const DEFAULT_LIMIT = 50;
|
|
10
12
|
const MAX_LIMIT = 500;
|
|
@@ -19,16 +21,47 @@ function routeDesignation(stateRoute) {
|
|
|
19
21
|
const number = stateRoute.replace(/^0+(?=\d)/, '');
|
|
20
22
|
return INTERSTATE_ROUTE_NUMBERS.has(number) ? `I-${number}` : `SR ${number}`;
|
|
21
23
|
}
|
|
24
|
+
const TollRateSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
tripName: z.string().optional().describe('Name of the tolled trip or lane segment.'),
|
|
27
|
+
stateRoute: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Route number as the feed reports it: bare and zero-padded, with no route type ("099", "405"). The rendered text shows the posted designation (SR 99, I-405).'),
|
|
31
|
+
travelDirection: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Direction code the feed assigns the toll trip: N, S, E, or W. On SR 99, SR 509, and SR 520 it is one fixed code per facility (S, S, and E) although trips run both ways, so read the direction of travel from the segment ends — startLocationName/endLocationName and startMilepost → endMilepost — not from this code.'),
|
|
35
|
+
startMilepost: z.number().optional().describe('Starting milepost of the toll segment.'),
|
|
36
|
+
endMilepost: z.number().optional().describe('Ending milepost of the toll segment.'),
|
|
37
|
+
tollRateInDollars: z.number().optional().describe('Current toll rate in US dollars.'),
|
|
38
|
+
message: z.string().optional().describe('Dynamic message associated with this toll.'),
|
|
39
|
+
startLocationName: z.string().optional().describe('Human-readable start location name.'),
|
|
40
|
+
endLocationName: z.string().optional().describe('Human-readable end location name.'),
|
|
41
|
+
startLatitude: z.number().optional().describe('Latitude of the segment start point.'),
|
|
42
|
+
startLongitude: z.number().optional().describe('Longitude of the segment start point.'),
|
|
43
|
+
endLatitude: z.number().optional().describe('Latitude of the segment end point.'),
|
|
44
|
+
endLongitude: z.number().optional().describe('Longitude of the segment end point.'),
|
|
45
|
+
timeUpdated: z.string().optional().describe('When this toll rate was last updated (ISO 8601).'),
|
|
46
|
+
})
|
|
47
|
+
.describe('Current toll rate for one segment or trip.');
|
|
22
48
|
export const getTollRates = tool('wsdot_get_toll_rates', {
|
|
23
49
|
title: 'Get Toll Rates',
|
|
24
50
|
description: 'Returns current dynamic toll rates for WA express lanes and tolled facilities: SR 99 (WSDOT Tunnel), ' +
|
|
25
51
|
'SR 167 HOT Lanes, I-405 Express Lanes, SR 509 tolled segment, and the SR 520 Bridge. ' +
|
|
26
52
|
'Rates are time-banded and change dynamically based on traffic conditions. ' +
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
53
|
+
'Filter to one facility with stateRoute ("SR 520", "520", "I-405", or "405" all work). ' +
|
|
54
|
+
'Each row reports stateRoute as the bare, zero-padded number the feed carries ("099", "405") — ' +
|
|
55
|
+
'the posted designation is in the rendered text. Results are paged — pass offset/limit to page ' +
|
|
56
|
+
'through the matching set. ' +
|
|
57
|
+
`A page ends at limit or at a ${PAGE_BYTE_BUDGET.toLocaleString('en-US')}-byte response budget, whichever comes first; the notice reports the next offset.`,
|
|
30
58
|
annotations: { readOnlyHint: true },
|
|
31
59
|
input: z.object({
|
|
60
|
+
stateRoute: z
|
|
61
|
+
.string()
|
|
62
|
+
.max(MAX_FILTER_LENGTH)
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Tolled route to filter by. Accepts natural forms — "SR 520", "520", "0520", "I-405", "405" — matched case- and space-insensitively against each row\'s posted designation, so a route-type prefix must agree: "SR 405" matches nothing because the I-405 Express Lanes are an Interstate. A route with no tolled facility returns an empty page whose notice names the tolled routes. Omit to include every facility.'),
|
|
32
65
|
offset: z
|
|
33
66
|
.number()
|
|
34
67
|
.int()
|
|
@@ -41,52 +74,39 @@ export const getTollRates = tool('wsdot_get_toll_rates', {
|
|
|
41
74
|
.min(1)
|
|
42
75
|
.max(MAX_LIMIT)
|
|
43
76
|
.optional()
|
|
44
|
-
.describe(`Maximum toll rates to return in this page (1–${MAX_LIMIT}). Defaults to ${DEFAULT_LIMIT}.`),
|
|
77
|
+
.describe(`Maximum toll rates to return in this page (1–${MAX_LIMIT}). Defaults to ${DEFAULT_LIMIT}. A large page ends sooner, at the ${PAGE_BYTE_BUDGET.toLocaleString('en-US')}-byte response budget.`),
|
|
45
78
|
}),
|
|
46
79
|
output: z.object({
|
|
47
80
|
rates: z
|
|
48
|
-
.array(
|
|
49
|
-
.
|
|
50
|
-
tripName: z.string().optional().describe('Name of the tolled trip or lane segment.'),
|
|
51
|
-
stateRoute: z.string().optional().describe('State route number.'),
|
|
52
|
-
travelDirection: z
|
|
53
|
-
.string()
|
|
54
|
-
.optional()
|
|
55
|
-
.describe('Travel direction code for this toll segment: N (north), S (south), E (east), W (west).'),
|
|
56
|
-
startMilepost: z.number().optional().describe('Starting milepost of the toll segment.'),
|
|
57
|
-
endMilepost: z.number().optional().describe('Ending milepost of the toll segment.'),
|
|
58
|
-
tollRateInDollars: z.number().optional().describe('Current toll rate in US dollars.'),
|
|
59
|
-
message: z.string().optional().describe('Dynamic message associated with this toll.'),
|
|
60
|
-
startLocationName: z
|
|
61
|
-
.string()
|
|
62
|
-
.optional()
|
|
63
|
-
.describe('Human-readable start location name.'),
|
|
64
|
-
endLocationName: z.string().optional().describe('Human-readable end location name.'),
|
|
65
|
-
startLatitude: z.number().optional().describe('Latitude of the segment start point.'),
|
|
66
|
-
startLongitude: z.number().optional().describe('Longitude of the segment start point.'),
|
|
67
|
-
endLatitude: z.number().optional().describe('Latitude of the segment end point.'),
|
|
68
|
-
endLongitude: z.number().optional().describe('Longitude of the segment end point.'),
|
|
69
|
-
timeUpdated: z
|
|
70
|
-
.string()
|
|
71
|
-
.optional()
|
|
72
|
-
.describe('When this toll rate was last updated (ISO 8601).'),
|
|
73
|
-
})
|
|
74
|
-
.describe('Current toll rate for one segment or trip.'))
|
|
75
|
-
.describe('Current toll rates for all active tolled facilities.'),
|
|
81
|
+
.array(TollRateSchema)
|
|
82
|
+
.describe('Current toll rates for the matching tolled facilities.'),
|
|
76
83
|
}),
|
|
77
84
|
enrichment: {
|
|
78
85
|
totalCount: z
|
|
79
86
|
.number()
|
|
80
|
-
.describe('Total toll rate entries across all pages (not just this page).'),
|
|
87
|
+
.describe('Total toll rate entries matching the filter across all pages (not just this page).'),
|
|
81
88
|
nextOffset: z
|
|
82
89
|
.number()
|
|
83
90
|
.nullable()
|
|
84
91
|
.describe('Offset to pass to retrieve the next page, or null when this is the last page.'),
|
|
85
92
|
hasMore: z.boolean().describe('True when more toll rates remain beyond the current page.'),
|
|
93
|
+
appliedFilters: z
|
|
94
|
+
.object({
|
|
95
|
+
stateRoute: z.string().optional().describe('State route filter applied.'),
|
|
96
|
+
})
|
|
97
|
+
.optional()
|
|
98
|
+
.describe('Active filters applied to the toll rates. Absent when no filter was applied.'),
|
|
86
99
|
notice: z
|
|
87
100
|
.string()
|
|
88
101
|
.optional()
|
|
89
|
-
.describe('Informational note about the page window, or guidance when no toll rate data is available or the offset ran past the end.'),
|
|
102
|
+
.describe('Informational note about the page window, or guidance when no toll rate data is available, the route has no tolled facility, or the offset ran past the end.'),
|
|
103
|
+
},
|
|
104
|
+
enrichmentTrailer: {
|
|
105
|
+
appliedFilters: {
|
|
106
|
+
render: (filters) => filters?.stateRoute
|
|
107
|
+
? `**Applied Filters:**\n- **Route:** ${filters.stateRoute}`
|
|
108
|
+
: '**Applied Filters:** none',
|
|
109
|
+
},
|
|
90
110
|
},
|
|
91
111
|
errors: [
|
|
92
112
|
{
|
|
@@ -95,6 +115,7 @@ export const getTollRates = tool('wsdot_get_toll_rates', {
|
|
|
95
115
|
when: 'WSDOT Traffic API is unreachable or returns a non-2xx response after retries.',
|
|
96
116
|
retryable: true,
|
|
97
117
|
recovery: 'Retry in 30 seconds. If the issue persists, check wsdot.wa.gov for service status.',
|
|
118
|
+
thrownBy: 'service',
|
|
98
119
|
},
|
|
99
120
|
{
|
|
100
121
|
reason: 'invalid_access_code',
|
|
@@ -102,30 +123,55 @@ export const getTollRates = tool('wsdot_get_toll_rates', {
|
|
|
102
123
|
when: 'WSDOT rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.',
|
|
103
124
|
retryable: false,
|
|
104
125
|
recovery: 'Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.',
|
|
126
|
+
thrownBy: 'service',
|
|
105
127
|
},
|
|
106
128
|
],
|
|
107
129
|
async handler(input, ctx) {
|
|
130
|
+
const stateRoute = input.stateRoute?.trim() || undefined;
|
|
108
131
|
const allRates = await getTrafficApiService().getTollRates(ctx);
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
// The feed value carries no route type, so it is matched as its posted designation — a bare
|
|
133
|
+
// "405" would otherwise let "SR 405" return the I-405 Express Lanes.
|
|
134
|
+
const matched = stateRoute
|
|
135
|
+
? allRates.filter((r) => r.stateRoute != null && routeMatches(stateRoute, routeDesignation(r.stateRoute)))
|
|
136
|
+
: allRates;
|
|
137
|
+
// Page the matching set so structuredContent and content[] carry the identical page (the
|
|
138
|
+
// service stays fetch-only; filtering and paging are tool-handler concerns). totalCount
|
|
139
|
+
// stays the full match count so the agent knows how much lies beyond this page.
|
|
140
|
+
const totalCount = matched.length;
|
|
113
141
|
const offset = input.offset ?? 0;
|
|
114
142
|
const limit = input.limit ?? DEFAULT_LIMIT;
|
|
115
|
-
const
|
|
143
|
+
const requested = matched.slice(offset, offset + limit);
|
|
144
|
+
const rates = fitPageToBudget(requested, renderTollRate, stateRoute);
|
|
116
145
|
const hasMore = offset + rates.length < totalCount;
|
|
117
146
|
const nextOffset = hasMore ? offset + rates.length : null;
|
|
118
147
|
ctx.log.info('Toll rates fetched', { totalCount, offset, limit, returned: rates.length });
|
|
119
|
-
ctx.enrich({
|
|
120
|
-
|
|
148
|
+
ctx.enrich({
|
|
149
|
+
totalCount,
|
|
150
|
+
nextOffset,
|
|
151
|
+
hasMore,
|
|
152
|
+
...(stateRoute && { appliedFilters: { stateRoute } }),
|
|
153
|
+
});
|
|
154
|
+
if (allRates.length === 0) {
|
|
121
155
|
ctx.enrich.notice('No toll rate data available. The WSDOT API may be temporarily unavailable — retry in 30 seconds.');
|
|
122
156
|
}
|
|
157
|
+
else if (totalCount === 0) {
|
|
158
|
+
// Dedupe the designations, not the raw values — "099" and "99" are one facility.
|
|
159
|
+
const tolled = [
|
|
160
|
+
...new Set(allRates
|
|
161
|
+
.flatMap((r) => (r.stateRoute ? [r.stateRoute] : []))
|
|
162
|
+
.toSorted((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10))
|
|
163
|
+
.map(routeDesignation)),
|
|
164
|
+
];
|
|
165
|
+
ctx.enrich.notice(`No tolled facility on ${stateRoute}. The feed carries tolls on ${tolled.join(', ')} — pass one of those, or omit stateRoute for every facility.`);
|
|
166
|
+
}
|
|
123
167
|
else if (rates.length === 0) {
|
|
124
168
|
ctx.enrich.notice(`Offset ${offset} is past the end of ${totalCount} toll rate entries. Use an offset between 0 and ${totalCount - 1}.`);
|
|
125
169
|
}
|
|
126
170
|
else {
|
|
127
|
-
const
|
|
128
|
-
ctx.enrich.notice(
|
|
171
|
+
const shown = `Showing toll rates ${offset + 1}–${offset + rates.length} of ${totalCount}.`;
|
|
172
|
+
ctx.enrich.notice(nextOffset === null
|
|
173
|
+
? shown
|
|
174
|
+
: `${shown} ${continuationNotice(nextOffset, rates.length < requested.length)}`);
|
|
129
175
|
}
|
|
130
176
|
return { rates };
|
|
131
177
|
},
|
|
@@ -133,42 +179,42 @@ export const getTollRates = tool('wsdot_get_toll_rates', {
|
|
|
133
179
|
if (result.rates.length === 0) {
|
|
134
180
|
return [{ type: 'text', text: 'No toll rate data available.' }];
|
|
135
181
|
}
|
|
136
|
-
|
|
137
|
-
for (const r of result.rates) {
|
|
138
|
-
// tripName is an opaque upstream key ("099tp03268"); the endpoint names read as a segment,
|
|
139
|
-
// so they lead. A segment whose ends carry the same name collapses to one.
|
|
140
|
-
const ends = [r.startLocationName, r.endLocationName].filter(Boolean);
|
|
141
|
-
const segment = [...new Set(ends)].join(' → ');
|
|
142
|
-
lines.push(`### ${segment || r.tripName || 'Toll segment'}`);
|
|
143
|
-
if (r.tripName)
|
|
144
|
-
lines.push(`**Trip:** ${r.tripName}`);
|
|
145
|
-
if (r.stateRoute)
|
|
146
|
-
lines.push(`**Route:** ${routeDesignation(r.stateRoute)}`);
|
|
147
|
-
if (r.travelDirection)
|
|
148
|
-
lines.push(`**Direction:** ${r.travelDirection}`);
|
|
149
|
-
if (r.startLocationName)
|
|
150
|
-
lines.push(`**From:** ${r.startLocationName}`);
|
|
151
|
-
if (r.endLocationName)
|
|
152
|
-
lines.push(`**To:** ${r.endLocationName}`);
|
|
153
|
-
if (r.startMilepost != null)
|
|
154
|
-
lines.push(`**Start MP:** ${r.startMilepost}`);
|
|
155
|
-
if (r.endMilepost != null)
|
|
156
|
-
lines.push(`**End MP:** ${r.endMilepost}`);
|
|
157
|
-
if (r.tollRateInDollars != null)
|
|
158
|
-
lines.push(`**Rate:** $${r.tollRateInDollars.toFixed(2)}`);
|
|
159
|
-
if (r.message)
|
|
160
|
-
lines.push(`**Message:** ${r.message}`);
|
|
161
|
-
const startCoords = coordinatePair(r.startLatitude, r.startLongitude);
|
|
162
|
-
if (startCoords)
|
|
163
|
-
lines.push(`**Start Coords:** ${startCoords}`);
|
|
164
|
-
const endCoords = coordinatePair(r.endLatitude, r.endLongitude);
|
|
165
|
-
if (endCoords)
|
|
166
|
-
lines.push(`**End Coords:** ${endCoords}`);
|
|
167
|
-
if (r.timeUpdated)
|
|
168
|
-
lines.push(`**Updated:** ${r.timeUpdated}`);
|
|
169
|
-
lines.push('');
|
|
170
|
-
}
|
|
171
|
-
return [{ type: 'text', text: lines.join('\n') }];
|
|
182
|
+
return [{ type: 'text', text: renderPage(result.rates, renderTollRate) }];
|
|
172
183
|
},
|
|
173
184
|
});
|
|
185
|
+
/** One toll rate's `content[]` block — shared by `format()` and the page-budget charge. */
|
|
186
|
+
function renderTollRate(r) {
|
|
187
|
+
// tripName is an opaque upstream key ("099tp03268"); the endpoint names read as a segment,
|
|
188
|
+
// so they lead. A segment whose ends carry the same name collapses to one.
|
|
189
|
+
const ends = [r.startLocationName, r.endLocationName].filter(Boolean);
|
|
190
|
+
const segment = [...new Set(ends)].join(' → ');
|
|
191
|
+
const lines = [`### ${segment || r.tripName || 'Toll segment'}`];
|
|
192
|
+
if (r.tripName)
|
|
193
|
+
lines.push(`**Trip:** ${r.tripName}`);
|
|
194
|
+
if (r.stateRoute)
|
|
195
|
+
lines.push(`**Route:** ${routeDesignation(r.stateRoute)}`);
|
|
196
|
+
if (r.travelDirection)
|
|
197
|
+
lines.push(`**Direction:** ${r.travelDirection}`);
|
|
198
|
+
if (r.startLocationName)
|
|
199
|
+
lines.push(`**From:** ${r.startLocationName}`);
|
|
200
|
+
if (r.endLocationName)
|
|
201
|
+
lines.push(`**To:** ${r.endLocationName}`);
|
|
202
|
+
if (r.startMilepost != null)
|
|
203
|
+
lines.push(`**Start MP:** ${r.startMilepost}`);
|
|
204
|
+
if (r.endMilepost != null)
|
|
205
|
+
lines.push(`**End MP:** ${r.endMilepost}`);
|
|
206
|
+
if (r.tollRateInDollars != null)
|
|
207
|
+
lines.push(`**Rate:** $${r.tollRateInDollars.toFixed(2)}`);
|
|
208
|
+
if (r.message)
|
|
209
|
+
lines.push(`**Message:** ${r.message}`);
|
|
210
|
+
const startCoords = coordinatePair(r.startLatitude, r.startLongitude);
|
|
211
|
+
if (startCoords)
|
|
212
|
+
lines.push(`**Start Coords:** ${startCoords}`);
|
|
213
|
+
const endCoords = coordinatePair(r.endLatitude, r.endLongitude);
|
|
214
|
+
if (endCoords)
|
|
215
|
+
lines.push(`**End Coords:** ${endCoords}`);
|
|
216
|
+
if (r.timeUpdated)
|
|
217
|
+
lines.push(`**Updated:** ${r.timeUpdated}`);
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
174
220
|
//# sourceMappingURL=get-toll-rates.tool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-toll-rates.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-toll-rates.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAE7E,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;GAIG;AACH,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAExF,oGAAoG;AACpG,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACnD,OAAO,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"get-toll-rates.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-toll-rates.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,12 +35,14 @@ export declare const getTravelTimes: import("@cyanheads/mcp-ts-core").ToolDefini
|
|
|
35
35
|
readonly when: "WSDOT Traffic API is unreachable or returns a non-2xx response after retries.";
|
|
36
36
|
readonly retryable: true;
|
|
37
37
|
readonly recovery: "Retry in 30 seconds. If the issue persists, check wsdot.wa.gov for service status.";
|
|
38
|
+
readonly thrownBy: "service";
|
|
38
39
|
}, {
|
|
39
40
|
readonly reason: "invalid_access_code";
|
|
40
41
|
readonly code: JsonRpcErrorCode.ConfigurationError;
|
|
41
42
|
readonly when: "WSDOT rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.";
|
|
42
43
|
readonly retryable: false;
|
|
43
44
|
readonly recovery: "Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.";
|
|
45
|
+
readonly thrownBy: "service";
|
|
44
46
|
}], {
|
|
45
47
|
readonly totalCount: z.ZodNumber;
|
|
46
48
|
readonly nextOffset: z.ZodNullable<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-travel-times.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-travel-times.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"get-travel-times.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-travel-times.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAqEjE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2JzB,CAAC"}
|
|
@@ -4,8 +4,55 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { continuationNotice, fitPageToBudget, MAX_FILTER_LENGTH, PAGE_BYTE_BUDGET, renderPage, } from '../page-budget.js';
|
|
7
8
|
import { routeMatches } from '../../../services/traffic/route-match.js';
|
|
8
9
|
import { getTrafficApiService } from '../../../services/traffic/traffic-service.js';
|
|
10
|
+
const CorridorSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
travelTimeId: z.number().optional().describe('Unique corridor identifier.'),
|
|
13
|
+
name: z.string().optional().describe('Corridor name (e.g. "I-5: Northgate to Downtown").'),
|
|
14
|
+
description: z.string().optional().describe('Additional corridor description.'),
|
|
15
|
+
currentTimeInMinutes: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Current travel time in minutes. Absent when WSDOT reports no measurement for the corridor — a reversible express lane closed in this direction reports none.'),
|
|
19
|
+
averageTimeInMinutes: z
|
|
20
|
+
.number()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Historical average travel time in minutes. Absent when WSDOT reports no measurement for the corridor.'),
|
|
23
|
+
delayInMinutes: z
|
|
24
|
+
.number()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Delay above average in minutes. Positive means congestion. Absent when either travel time is unavailable.'),
|
|
27
|
+
timeUpdated: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('When the travel time data was last updated (ISO 8601).'),
|
|
31
|
+
distanceInMiles: z.number().optional().describe('Corridor distance in miles.'),
|
|
32
|
+
startPoint: z
|
|
33
|
+
.object({
|
|
34
|
+
roadName: z.string().optional().describe('Road name at the start.'),
|
|
35
|
+
direction: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Travel direction code: N (north), S (south), E (east), W (west).'),
|
|
39
|
+
milePost: z.number().optional().describe('Starting milepost.'),
|
|
40
|
+
})
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Start of the measured corridor.'),
|
|
43
|
+
endPoint: z
|
|
44
|
+
.object({
|
|
45
|
+
roadName: z.string().optional().describe('Road name at the end.'),
|
|
46
|
+
direction: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe('Travel direction code: N (north), S (south), E (east), W (west).'),
|
|
50
|
+
milePost: z.number().optional().describe('Ending milepost.'),
|
|
51
|
+
})
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('End of the measured corridor.'),
|
|
54
|
+
})
|
|
55
|
+
.describe('Travel time data for one highway corridor.');
|
|
9
56
|
const DEFAULT_LIMIT = 50;
|
|
10
57
|
const MAX_LIMIT = 500;
|
|
11
58
|
export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
@@ -15,11 +62,13 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
15
62
|
'The route filter matches two ways: a route designation ("I-5", "5", "SR 520") returns every ' +
|
|
16
63
|
'corridor measured on that route, and any text also matches corridor names ("Everett"). ' +
|
|
17
64
|
'When current time exceeds average, the corridor is congested. ' +
|
|
18
|
-
'Results are paged — pass offset/limit to page through the full set
|
|
65
|
+
'Results are paged — pass offset/limit to page through the full set. ' +
|
|
66
|
+
`A page ends at limit or at a ${PAGE_BYTE_BUDGET.toLocaleString('en-US')}-byte response budget, whichever comes first; the notice reports the next offset.`,
|
|
19
67
|
annotations: { readOnlyHint: true },
|
|
20
68
|
input: z.object({
|
|
21
69
|
route: z
|
|
22
70
|
.string()
|
|
71
|
+
.max(MAX_FILTER_LENGTH)
|
|
23
72
|
.optional()
|
|
24
73
|
.describe('Optional filter. A route designation — "I-5", "5", "005", "SR 520", "520" — matches every ' +
|
|
25
74
|
'corridor whose start or end point is on that route, whichever form the feed reports. ' +
|
|
@@ -37,60 +86,10 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
37
86
|
.min(1)
|
|
38
87
|
.max(MAX_LIMIT)
|
|
39
88
|
.optional()
|
|
40
|
-
.describe(`Maximum corridors to return in this page (1–${MAX_LIMIT}). Defaults to ${DEFAULT_LIMIT}.`),
|
|
89
|
+
.describe(`Maximum corridors to return in this page (1–${MAX_LIMIT}). Defaults to ${DEFAULT_LIMIT}. A large page ends sooner, at the ${PAGE_BYTE_BUDGET.toLocaleString('en-US')}-byte response budget.`),
|
|
41
90
|
}),
|
|
42
91
|
output: z.object({
|
|
43
|
-
corridors: z
|
|
44
|
-
.array(z
|
|
45
|
-
.object({
|
|
46
|
-
travelTimeId: z.number().optional().describe('Unique corridor identifier.'),
|
|
47
|
-
name: z
|
|
48
|
-
.string()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe('Corridor name (e.g. "I-5: Northgate to Downtown").'),
|
|
51
|
-
description: z.string().optional().describe('Additional corridor description.'),
|
|
52
|
-
currentTimeInMinutes: z
|
|
53
|
-
.number()
|
|
54
|
-
.optional()
|
|
55
|
-
.describe('Current travel time in minutes. Absent when WSDOT reports no measurement for the corridor — a reversible express lane closed in this direction reports none.'),
|
|
56
|
-
averageTimeInMinutes: z
|
|
57
|
-
.number()
|
|
58
|
-
.optional()
|
|
59
|
-
.describe('Historical average travel time in minutes. Absent when WSDOT reports no measurement for the corridor.'),
|
|
60
|
-
delayInMinutes: z
|
|
61
|
-
.number()
|
|
62
|
-
.optional()
|
|
63
|
-
.describe('Delay above average in minutes. Positive means congestion. Absent when either travel time is unavailable.'),
|
|
64
|
-
timeUpdated: z
|
|
65
|
-
.string()
|
|
66
|
-
.optional()
|
|
67
|
-
.describe('When the travel time data was last updated (ISO 8601).'),
|
|
68
|
-
distanceInMiles: z.number().optional().describe('Corridor distance in miles.'),
|
|
69
|
-
startPoint: z
|
|
70
|
-
.object({
|
|
71
|
-
roadName: z.string().optional().describe('Road name at the start.'),
|
|
72
|
-
direction: z
|
|
73
|
-
.string()
|
|
74
|
-
.optional()
|
|
75
|
-
.describe('Travel direction code: N (north), S (south), E (east), W (west).'),
|
|
76
|
-
milePost: z.number().optional().describe('Starting milepost.'),
|
|
77
|
-
})
|
|
78
|
-
.optional()
|
|
79
|
-
.describe('Start of the measured corridor.'),
|
|
80
|
-
endPoint: z
|
|
81
|
-
.object({
|
|
82
|
-
roadName: z.string().optional().describe('Road name at the end.'),
|
|
83
|
-
direction: z
|
|
84
|
-
.string()
|
|
85
|
-
.optional()
|
|
86
|
-
.describe('Travel direction code: N (north), S (south), E (east), W (west).'),
|
|
87
|
-
milePost: z.number().optional().describe('Ending milepost.'),
|
|
88
|
-
})
|
|
89
|
-
.optional()
|
|
90
|
-
.describe('End of the measured corridor.'),
|
|
91
|
-
})
|
|
92
|
-
.describe('Travel time data for one highway corridor.'))
|
|
93
|
-
.describe('Travel time corridors matching the filter.'),
|
|
92
|
+
corridors: z.array(CorridorSchema).describe('Travel time corridors matching the filter.'),
|
|
94
93
|
}),
|
|
95
94
|
enrichment: {
|
|
96
95
|
totalCount: z
|
|
@@ -117,6 +116,7 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
117
116
|
when: 'WSDOT Traffic API is unreachable or returns a non-2xx response after retries.',
|
|
118
117
|
retryable: true,
|
|
119
118
|
recovery: 'Retry in 30 seconds. If the issue persists, check wsdot.wa.gov for service status.',
|
|
119
|
+
thrownBy: 'service',
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
reason: 'invalid_access_code',
|
|
@@ -124,6 +124,7 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
124
124
|
when: 'WSDOT rejected the request because WSDOT_ACCESS_CODE is missing, invalid, or not registered.',
|
|
125
125
|
retryable: false,
|
|
126
126
|
recovery: 'Register an access code at https://wsdot.wa.gov/traffic/api/, set WSDOT_ACCESS_CODE on the server, and restart it.',
|
|
127
|
+
thrownBy: 'service',
|
|
127
128
|
},
|
|
128
129
|
],
|
|
129
130
|
async handler(input, ctx) {
|
|
@@ -142,12 +143,13 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
142
143
|
const totalCount = filtered.length;
|
|
143
144
|
const offset = input.offset ?? 0;
|
|
144
145
|
const limit = input.limit ?? DEFAULT_LIMIT;
|
|
145
|
-
const
|
|
146
|
+
const requested = filtered.slice(offset, offset + limit).map((t) => ({
|
|
146
147
|
...t,
|
|
147
148
|
delayInMinutes: t.currentTimeInMinutes != null && t.averageTimeInMinutes != null
|
|
148
149
|
? t.currentTimeInMinutes - t.averageTimeInMinutes
|
|
149
150
|
: undefined,
|
|
150
151
|
}));
|
|
152
|
+
const corridors = fitPageToBudget(requested, renderCorridor, routeFilter);
|
|
151
153
|
const hasMore = offset + corridors.length < totalCount;
|
|
152
154
|
const nextOffset = hasMore ? offset + corridors.length : null;
|
|
153
155
|
ctx.log.info('Travel times fetched', {
|
|
@@ -167,8 +169,10 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
167
169
|
ctx.enrich.notice(`Offset ${offset} is past the end of ${totalCount} matching corridors. Use an offset between 0 and ${totalCount - 1}.`);
|
|
168
170
|
}
|
|
169
171
|
else {
|
|
170
|
-
const
|
|
171
|
-
ctx.enrich.notice(
|
|
172
|
+
const shown = `Showing corridors ${offset + 1}–${offset + corridors.length} of ${totalCount}.`;
|
|
173
|
+
ctx.enrich.notice(nextOffset === null
|
|
174
|
+
? shown
|
|
175
|
+
: `${shown} ${continuationNotice(nextOffset, corridors.length < requested.length)}`);
|
|
172
176
|
}
|
|
173
177
|
return { corridors };
|
|
174
178
|
},
|
|
@@ -176,51 +180,51 @@ export const getTravelTimes = tool('wsdot_get_travel_times', {
|
|
|
176
180
|
if (result.corridors.length === 0) {
|
|
177
181
|
return [{ type: 'text', text: 'No corridors matched.' }];
|
|
178
182
|
}
|
|
179
|
-
|
|
180
|
-
for (const c of result.corridors) {
|
|
181
|
-
lines.push(`### ${c.name ?? 'Corridor'}`);
|
|
182
|
-
if (c.description)
|
|
183
|
-
lines.push(c.description);
|
|
184
|
-
lines.push(c.currentTimeInMinutes != null
|
|
185
|
-
? `**Current:** ${c.currentTimeInMinutes} min`
|
|
186
|
-
: '**Current:** Not available — WSDOT reports no measurement for this corridor');
|
|
187
|
-
if (c.averageTimeInMinutes != null)
|
|
188
|
-
lines.push(`**Average:** ${c.averageTimeInMinutes} min`);
|
|
189
|
-
if (c.delayInMinutes != null) {
|
|
190
|
-
const sign = c.delayInMinutes > 0 ? '+' : '';
|
|
191
|
-
lines.push(`**Delay:** ${sign}${c.delayInMinutes} min${c.delayInMinutes > 0 ? ' (congested)' : ''}`);
|
|
192
|
-
}
|
|
193
|
-
if (c.distanceInMiles != null)
|
|
194
|
-
lines.push(`**Distance:** ${c.distanceInMiles} mi`);
|
|
195
|
-
if (c.startPoint) {
|
|
196
|
-
const sp = [
|
|
197
|
-
c.startPoint.roadName,
|
|
198
|
-
c.startPoint.direction,
|
|
199
|
-
c.startPoint.milePost != null ? `MP ${c.startPoint.milePost}` : undefined,
|
|
200
|
-
]
|
|
201
|
-
.filter(Boolean)
|
|
202
|
-
.join(' ');
|
|
203
|
-
if (sp)
|
|
204
|
-
lines.push(`**From:** ${sp}`);
|
|
205
|
-
}
|
|
206
|
-
if (c.endPoint) {
|
|
207
|
-
const ep = [
|
|
208
|
-
c.endPoint.roadName,
|
|
209
|
-
c.endPoint.direction,
|
|
210
|
-
c.endPoint.milePost != null ? `MP ${c.endPoint.milePost}` : undefined,
|
|
211
|
-
]
|
|
212
|
-
.filter(Boolean)
|
|
213
|
-
.join(' ');
|
|
214
|
-
if (ep)
|
|
215
|
-
lines.push(`**To:** ${ep}`);
|
|
216
|
-
}
|
|
217
|
-
if (c.timeUpdated)
|
|
218
|
-
lines.push(`**Updated:** ${c.timeUpdated}`);
|
|
219
|
-
if (c.travelTimeId != null)
|
|
220
|
-
lines.push(`**ID:** ${c.travelTimeId}`);
|
|
221
|
-
lines.push('');
|
|
222
|
-
}
|
|
223
|
-
return [{ type: 'text', text: lines.join('\n') }];
|
|
183
|
+
return [{ type: 'text', text: renderPage(result.corridors, renderCorridor) }];
|
|
224
184
|
},
|
|
225
185
|
});
|
|
186
|
+
/** One corridor's `content[]` block — shared by `format()` and the page-budget charge. */
|
|
187
|
+
function renderCorridor(c) {
|
|
188
|
+
const lines = [`### ${c.name ?? 'Corridor'}`];
|
|
189
|
+
if (c.description)
|
|
190
|
+
lines.push(c.description);
|
|
191
|
+
lines.push(c.currentTimeInMinutes != null
|
|
192
|
+
? `**Current:** ${c.currentTimeInMinutes} min`
|
|
193
|
+
: '**Current:** Not available — WSDOT reports no measurement for this corridor');
|
|
194
|
+
if (c.averageTimeInMinutes != null)
|
|
195
|
+
lines.push(`**Average:** ${c.averageTimeInMinutes} min`);
|
|
196
|
+
if (c.delayInMinutes != null) {
|
|
197
|
+
const sign = c.delayInMinutes > 0 ? '+' : '';
|
|
198
|
+
lines.push(`**Delay:** ${sign}${c.delayInMinutes} min${c.delayInMinutes > 0 ? ' (congested)' : ''}`);
|
|
199
|
+
}
|
|
200
|
+
if (c.distanceInMiles != null)
|
|
201
|
+
lines.push(`**Distance:** ${c.distanceInMiles} mi`);
|
|
202
|
+
if (c.startPoint) {
|
|
203
|
+
const sp = [
|
|
204
|
+
c.startPoint.roadName,
|
|
205
|
+
c.startPoint.direction,
|
|
206
|
+
c.startPoint.milePost != null ? `MP ${c.startPoint.milePost}` : undefined,
|
|
207
|
+
]
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
.join(' ');
|
|
210
|
+
if (sp)
|
|
211
|
+
lines.push(`**From:** ${sp}`);
|
|
212
|
+
}
|
|
213
|
+
if (c.endPoint) {
|
|
214
|
+
const ep = [
|
|
215
|
+
c.endPoint.roadName,
|
|
216
|
+
c.endPoint.direction,
|
|
217
|
+
c.endPoint.milePost != null ? `MP ${c.endPoint.milePost}` : undefined,
|
|
218
|
+
]
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.join(' ');
|
|
221
|
+
if (ep)
|
|
222
|
+
lines.push(`**To:** ${ep}`);
|
|
223
|
+
}
|
|
224
|
+
if (c.timeUpdated)
|
|
225
|
+
lines.push(`**Updated:** ${c.timeUpdated}`);
|
|
226
|
+
if (c.travelTimeId != null)
|
|
227
|
+
lines.push(`**ID:** ${c.travelTimeId}`);
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
226
230
|
//# sourceMappingURL=get-travel-times.tool.js.map
|