@cyanheads/noaa-marine-mcp-server 0.1.1
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 +387 -0
- package/CLAUDE.md +387 -0
- package/Dockerfile +99 -0
- package/LICENSE +201 -0
- package/README.md +300 -0
- package/changelog/0.1.x/0.1.1.md +28 -0
- package/changelog/template.md +127 -0
- package/dist/config/server-config.d.ts +12 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +20 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.d.ts +9 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js +96 -0
- package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts +105 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js +338 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts +40 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js +164 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts +59 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js +227 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts +60 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js +180 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts +65 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js +232 -0
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js.map +1 -0
- package/dist/services/coops/coops-service.d.ts +74 -0
- package/dist/services/coops/coops-service.d.ts.map +1 -0
- package/dist/services/coops/coops-service.js +217 -0
- package/dist/services/coops/coops-service.js.map +1 -0
- package/dist/services/coops/types.d.ts +66 -0
- package/dist/services/coops/types.d.ts.map +1 -0
- package/dist/services/coops/types.js +6 -0
- package/dist/services/coops/types.js.map +1 -0
- package/dist/services/ndbc/ndbc-service.d.ts +28 -0
- package/dist/services/ndbc/ndbc-service.d.ts.map +1 -0
- package/dist/services/ndbc/ndbc-service.js +213 -0
- package/dist/services/ndbc/ndbc-service.js.map +1 -0
- package/dist/services/ndbc/types.d.ts +60 -0
- package/dist/services/ndbc/types.d.ts.map +1 -0
- package/dist/services/ndbc/types.js +6 -0
- package/dist/services/ndbc/types.js.map +1 -0
- package/package.json +105 -0
- package/server.json +113 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CO-OPS Tides & Currents API service — station list cache and data fetching.
|
|
3
|
+
* @module services/coops/coops-service
|
|
4
|
+
*/
|
|
5
|
+
import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
6
|
+
import { fetchWithTimeout, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
7
|
+
const DATA_URL = 'https://api.tidesandcurrents.noaa.gov/api/prod/datagetter';
|
|
8
|
+
const MDAPI_URL = 'https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json';
|
|
9
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
10
|
+
export class CoopsService {
|
|
11
|
+
applicationId;
|
|
12
|
+
stationCache = new Map();
|
|
13
|
+
// AppConfig and StorageService accepted for init-pattern consistency; not used at runtime
|
|
14
|
+
constructor(_config, _storage, serverConfig) {
|
|
15
|
+
this.applicationId = serverConfig.applicationId;
|
|
16
|
+
}
|
|
17
|
+
/** Fetch (or return cached) station list for the given type. */
|
|
18
|
+
async getStations(type, ctx) {
|
|
19
|
+
const entry = this.stationCache.get(type);
|
|
20
|
+
if (entry && Date.now() - entry.fetchedAt < CACHE_TTL_MS) {
|
|
21
|
+
return entry.stations;
|
|
22
|
+
}
|
|
23
|
+
const stations = await withRetry(async () => {
|
|
24
|
+
const url = `${MDAPI_URL}?type=${type}&application=${encodeURIComponent(this.applicationId)}`;
|
|
25
|
+
const response = await fetchWithTimeout(url, 15_000, ctx, {
|
|
26
|
+
signal: ctx.signal,
|
|
27
|
+
});
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
this.detectHtmlError(text);
|
|
30
|
+
const parsed = JSON.parse(text);
|
|
31
|
+
return parsed.stations ?? [];
|
|
32
|
+
}, {
|
|
33
|
+
operation: `CoopsService.getStations(${type})`,
|
|
34
|
+
context: ctx,
|
|
35
|
+
baseDelayMs: 2000,
|
|
36
|
+
maxRetries: 2,
|
|
37
|
+
signal: ctx.signal,
|
|
38
|
+
});
|
|
39
|
+
this.stationCache.set(type, { stations, fetchedAt: Date.now() });
|
|
40
|
+
ctx.log.debug('CO-OPS station list cached', { type, count: stations.length });
|
|
41
|
+
return stations;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pre-warm the most-used station lists. Called during server setup.
|
|
45
|
+
* Errors are caught and logged — pre-warm is best-effort.
|
|
46
|
+
*/
|
|
47
|
+
async preWarm(ctx) {
|
|
48
|
+
await Promise.allSettled([
|
|
49
|
+
this.getStations('tidepredictions', ctx),
|
|
50
|
+
this.getStations('currentpredictions', ctx),
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
/** Fetch tide predictions for a station over a date range. */
|
|
54
|
+
async fetchTidePredictions(params, ctx) {
|
|
55
|
+
return await withRetry(async () => {
|
|
56
|
+
const url = this.buildDataUrl({
|
|
57
|
+
station: params.station,
|
|
58
|
+
product: 'predictions',
|
|
59
|
+
begin_date: params.begin_date,
|
|
60
|
+
end_date: params.end_date,
|
|
61
|
+
datum: params.datum,
|
|
62
|
+
time_zone: params.time_zone,
|
|
63
|
+
units: params.units,
|
|
64
|
+
interval: params.interval,
|
|
65
|
+
format: 'json',
|
|
66
|
+
});
|
|
67
|
+
const response = await fetchWithTimeout(url, 20_000, ctx, {
|
|
68
|
+
signal: ctx.signal,
|
|
69
|
+
});
|
|
70
|
+
const text = await response.text();
|
|
71
|
+
this.detectHtmlError(text);
|
|
72
|
+
const parsed = JSON.parse(text);
|
|
73
|
+
this.checkCoopsError(parsed);
|
|
74
|
+
const predictions = parsed.predictions ?? [];
|
|
75
|
+
// Station name isn't in this response; caller handles it
|
|
76
|
+
return { predictions, stationName: parsed.metadata?.name ?? params.station };
|
|
77
|
+
}, {
|
|
78
|
+
operation: 'CoopsService.fetchTidePredictions',
|
|
79
|
+
context: ctx,
|
|
80
|
+
baseDelayMs: 1000,
|
|
81
|
+
maxRetries: 3,
|
|
82
|
+
signal: ctx.signal,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Fetch observed water levels for a station. */
|
|
86
|
+
async fetchWaterLevel(params, ctx) {
|
|
87
|
+
return await withRetry(async () => {
|
|
88
|
+
const url = this.buildDataUrl({
|
|
89
|
+
station: params.station,
|
|
90
|
+
product: 'water_level',
|
|
91
|
+
begin_date: params.begin_date,
|
|
92
|
+
end_date: params.end_date,
|
|
93
|
+
datum: params.datum,
|
|
94
|
+
time_zone: params.time_zone,
|
|
95
|
+
units: params.units,
|
|
96
|
+
format: 'json',
|
|
97
|
+
});
|
|
98
|
+
const response = await fetchWithTimeout(url, 20_000, ctx, {
|
|
99
|
+
signal: ctx.signal,
|
|
100
|
+
});
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
this.detectHtmlError(text);
|
|
103
|
+
const parsed = JSON.parse(text);
|
|
104
|
+
this.checkCoopsError(parsed);
|
|
105
|
+
return { data: parsed.data ?? [], stationName: parsed.metadata?.name ?? params.station };
|
|
106
|
+
}, {
|
|
107
|
+
operation: 'CoopsService.fetchWaterLevel',
|
|
108
|
+
context: ctx,
|
|
109
|
+
baseDelayMs: 1000,
|
|
110
|
+
maxRetries: 3,
|
|
111
|
+
signal: ctx.signal,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/** Fetch water level predictions for the same date range as a water_level request (companion fetch). */
|
|
115
|
+
async fetchWaterLevelPredictions(params, ctx) {
|
|
116
|
+
return await withRetry(async () => {
|
|
117
|
+
const url = this.buildDataUrl({
|
|
118
|
+
station: params.station,
|
|
119
|
+
product: 'predictions',
|
|
120
|
+
begin_date: params.begin_date,
|
|
121
|
+
end_date: params.end_date,
|
|
122
|
+
datum: params.datum,
|
|
123
|
+
time_zone: params.time_zone,
|
|
124
|
+
units: params.units,
|
|
125
|
+
interval: '6',
|
|
126
|
+
format: 'json',
|
|
127
|
+
});
|
|
128
|
+
const response = await fetchWithTimeout(url, 20_000, ctx, {
|
|
129
|
+
signal: ctx.signal,
|
|
130
|
+
});
|
|
131
|
+
const text = await response.text();
|
|
132
|
+
this.detectHtmlError(text);
|
|
133
|
+
const parsed = JSON.parse(text);
|
|
134
|
+
// Predictions can fail for some stations — caller degrades gracefully
|
|
135
|
+
if (parsed.error)
|
|
136
|
+
return [];
|
|
137
|
+
return (parsed.predictions ?? []);
|
|
138
|
+
}, {
|
|
139
|
+
operation: 'CoopsService.fetchWaterLevelPredictions',
|
|
140
|
+
context: ctx,
|
|
141
|
+
baseDelayMs: 1000,
|
|
142
|
+
maxRetries: 2,
|
|
143
|
+
signal: ctx.signal,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/** Fetch current predictions for a station. */
|
|
147
|
+
async fetchCurrentPredictions(params, ctx) {
|
|
148
|
+
return await withRetry(async () => {
|
|
149
|
+
// CO-OPS currents_predictions API accepts "6" not "6min"; translate the user-facing enum.
|
|
150
|
+
const apiInterval = params.interval === '6min' ? '6' : params.interval;
|
|
151
|
+
const url = this.buildDataUrl({
|
|
152
|
+
station: params.station,
|
|
153
|
+
product: 'currents_predictions',
|
|
154
|
+
begin_date: params.begin_date,
|
|
155
|
+
end_date: params.end_date,
|
|
156
|
+
time_zone: params.time_zone,
|
|
157
|
+
units: params.units,
|
|
158
|
+
interval: apiInterval,
|
|
159
|
+
format: 'json',
|
|
160
|
+
});
|
|
161
|
+
const response = await fetchWithTimeout(url, 20_000, ctx, {
|
|
162
|
+
signal: ctx.signal,
|
|
163
|
+
});
|
|
164
|
+
const text = await response.text();
|
|
165
|
+
this.detectHtmlError(text);
|
|
166
|
+
const parsed = JSON.parse(text);
|
|
167
|
+
this.checkCoopsError(parsed);
|
|
168
|
+
// CO-OPS MAX_SLACK returns cp as a string (e.g. "Currents are weak and variable")
|
|
169
|
+
// instead of an array when there are no significant flood/ebb/slack events.
|
|
170
|
+
const rawCp = parsed.current_predictions?.cp;
|
|
171
|
+
const cp = Array.isArray(rawCp) ? rawCp : [];
|
|
172
|
+
const stationName = parsed.metadata?.name ?? params.station;
|
|
173
|
+
if (params.interval === 'MAX_SLACK') {
|
|
174
|
+
return { events: cp, stationName };
|
|
175
|
+
}
|
|
176
|
+
return { predictions: cp, stationName };
|
|
177
|
+
}, {
|
|
178
|
+
operation: 'CoopsService.fetchCurrentPredictions',
|
|
179
|
+
context: ctx,
|
|
180
|
+
baseDelayMs: 1000,
|
|
181
|
+
maxRetries: 3,
|
|
182
|
+
signal: ctx.signal,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
buildDataUrl(params) {
|
|
186
|
+
const p = new URLSearchParams({
|
|
187
|
+
...params,
|
|
188
|
+
application: this.applicationId,
|
|
189
|
+
});
|
|
190
|
+
return `${DATA_URL}?${p.toString()}`;
|
|
191
|
+
}
|
|
192
|
+
detectHtmlError(text) {
|
|
193
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
194
|
+
throw serviceUnavailable('CO-OPS API returned HTML instead of JSON — likely rate-limited or temporarily unavailable.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
checkCoopsError(parsed) {
|
|
198
|
+
const p = parsed;
|
|
199
|
+
if (p && typeof p === 'object' && 'error' in p && p.error) {
|
|
200
|
+
const errObj = p.error;
|
|
201
|
+
const message = errObj.message ?? 'Unknown CO-OPS error';
|
|
202
|
+
throw serviceUnavailable(`CO-OPS error: ${message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// --- Init/accessor pattern ---
|
|
207
|
+
let _service;
|
|
208
|
+
export function initCoopsService(config, storage, serverConfig) {
|
|
209
|
+
_service = new CoopsService(config, storage, serverConfig);
|
|
210
|
+
}
|
|
211
|
+
export function getCoopsService() {
|
|
212
|
+
if (!_service) {
|
|
213
|
+
throw new Error('CoopsService not initialized — call initCoopsService() in setup()');
|
|
214
|
+
}
|
|
215
|
+
return _service;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=coops-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coops-service.js","sourceRoot":"","sources":["../../../src/services/coops/coops-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type definitions for the CO-OPS Tides & Currents service.
|
|
3
|
+
* @module services/coops/types
|
|
4
|
+
*/
|
|
5
|
+
/** A station from the CO-OPS metadata API station list. */
|
|
6
|
+
export interface CoopsStation {
|
|
7
|
+
id: string;
|
|
8
|
+
lat: number;
|
|
9
|
+
lng: number;
|
|
10
|
+
name: string;
|
|
11
|
+
state?: string;
|
|
12
|
+
/** Station type code — R=reference, T=subordinate, S=secondary, ACT-prefix for currents */
|
|
13
|
+
type?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A tide/water-level data observation returned from the CO-OPS data endpoint. */
|
|
16
|
+
export interface CoopsDataRow {
|
|
17
|
+
/** Quality flag: p=preliminary, v=verified */
|
|
18
|
+
q?: string;
|
|
19
|
+
/** Standard deviation / sigma (water level only) */
|
|
20
|
+
s?: string;
|
|
21
|
+
/** ISO datetime string */
|
|
22
|
+
t: string;
|
|
23
|
+
/** Numeric value (water level in feet or metric equivalent) */
|
|
24
|
+
v: string;
|
|
25
|
+
}
|
|
26
|
+
/** A tidal prediction row. */
|
|
27
|
+
export interface CoopsPredictionRow {
|
|
28
|
+
t: string;
|
|
29
|
+
/** Tide type: H=high, L=low (hilo interval only) */
|
|
30
|
+
type?: string;
|
|
31
|
+
v: string;
|
|
32
|
+
}
|
|
33
|
+
/** A current prediction row (MAX_SLACK interval). */
|
|
34
|
+
export interface CoopsCurrentRow {
|
|
35
|
+
/** True bearing direction in degrees */
|
|
36
|
+
meanEbbDir?: string;
|
|
37
|
+
meanFloodDir?: string;
|
|
38
|
+
/** Event datetime */
|
|
39
|
+
Time: string;
|
|
40
|
+
/** Event type: flood, ebb, slack */
|
|
41
|
+
Type: string;
|
|
42
|
+
/** Speed in knots or m/s */
|
|
43
|
+
Velocity_Major?: string;
|
|
44
|
+
}
|
|
45
|
+
/** A current prediction row (6-min interval). */
|
|
46
|
+
export interface CoopsCurrent6MinRow {
|
|
47
|
+
/** Direction */
|
|
48
|
+
Direction?: string;
|
|
49
|
+
Time: string;
|
|
50
|
+
/** Speed */
|
|
51
|
+
Velocity_Major?: string;
|
|
52
|
+
}
|
|
53
|
+
/** CO-OPS data API error envelope shape. */
|
|
54
|
+
export interface CoopsErrorResponse {
|
|
55
|
+
error: {
|
|
56
|
+
message: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** CO-OPS mdapi station list response. */
|
|
60
|
+
export interface CoopsStationListResponse {
|
|
61
|
+
count?: number;
|
|
62
|
+
stations: CoopsStation[];
|
|
63
|
+
}
|
|
64
|
+
/** Supported CO-OPS station list types for the metadata endpoint. */
|
|
65
|
+
export type CoopsStationType = 'tidepredictions' | 'currentpredictions' | 'waterlevels';
|
|
66
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/coops/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,+DAA+D;IAC/D,CAAC,EAAE,MAAM,CAAC;CACX;AAED,8BAA8B;AAC9B,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;CACX;AAED,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,gBAAgB;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5B;AAED,0CAA0C;AAC1C,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/coops/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview NDBC (National Data Buoy Center) service — active stations XML cache and realtime text parser.
|
|
3
|
+
* @module services/ndbc/ndbc-service
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import type { NdbcObservation, NdbcStation } from './types.js';
|
|
7
|
+
export declare class NdbcService {
|
|
8
|
+
private stationCache;
|
|
9
|
+
/** Fetch (or return cached) NDBC active stations. */
|
|
10
|
+
getActiveStations(ctx: Context): Promise<NdbcStation[]>;
|
|
11
|
+
/** Pre-warm the active station cache. Called during server setup. */
|
|
12
|
+
preWarm(ctx: Context): Promise<void>;
|
|
13
|
+
/** Fetch the most recent observation for a buoy from the realtime2 text feed. */
|
|
14
|
+
fetchObservation(stationId: string, ctx: Context): Promise<NdbcObservation>;
|
|
15
|
+
/** Parse NDBC active stations XML into NdbcStation array. */
|
|
16
|
+
private parseActiveStationsXml;
|
|
17
|
+
/**
|
|
18
|
+
* Parse NDBC fixed-width realtime2 text file into a NdbcObservation.
|
|
19
|
+
* Line 1: header (# + column names)
|
|
20
|
+
* Line 2: units row (ignored)
|
|
21
|
+
* Lines 3+: data rows most recent first
|
|
22
|
+
* MM = missing sensor value → null
|
|
23
|
+
*/
|
|
24
|
+
parseRealtimeText(text: string, stationId: string): NdbcObservation;
|
|
25
|
+
}
|
|
26
|
+
export declare function initNdbcService(): void;
|
|
27
|
+
export declare function getNdbcService(): NdbcService;
|
|
28
|
+
//# sourceMappingURL=ndbc-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ndbc-service.d.ts","sourceRoot":"","sources":["../../../src/services/ndbc/ndbc-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAGtD,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAY/D,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAA6B;IAEjD,qDAAqD;IAC/C,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiC7D,qEAAqE;IAC/D,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,iFAAiF;IAC3E,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAyBjF,6DAA6D;IAC7D,OAAO,CAAC,sBAAsB;IA4C9B;;;;;;OAMG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe;CA+GpE;AAMD,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview NDBC (National Data Buoy Center) service — active stations XML cache and realtime text parser.
|
|
3
|
+
* @module services/ndbc/ndbc-service
|
|
4
|
+
*/
|
|
5
|
+
import { notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
6
|
+
import { fetchWithTimeout, withRetry } from '@cyanheads/mcp-ts-core/utils';
|
|
7
|
+
const ACTIVE_STATIONS_URL = 'https://www.ndbc.noaa.gov/activestations.xml';
|
|
8
|
+
const REALTIME_URL = (id) => `https://www.ndbc.noaa.gov/data/realtime2/${id.toUpperCase()}.txt`;
|
|
9
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
10
|
+
export class NdbcService {
|
|
11
|
+
stationCache = null;
|
|
12
|
+
/** Fetch (or return cached) NDBC active stations. */
|
|
13
|
+
async getActiveStations(ctx) {
|
|
14
|
+
if (this.stationCache && Date.now() - this.stationCache.fetchedAt < CACHE_TTL_MS) {
|
|
15
|
+
return this.stationCache.stations;
|
|
16
|
+
}
|
|
17
|
+
const stations = await withRetry(async () => {
|
|
18
|
+
const response = await fetchWithTimeout(ACTIVE_STATIONS_URL, 20_000, ctx, { signal: ctx.signal });
|
|
19
|
+
const text = await response.text();
|
|
20
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
21
|
+
throw serviceUnavailable('NDBC active stations returned HTML — service may be down.');
|
|
22
|
+
}
|
|
23
|
+
return this.parseActiveStationsXml(text);
|
|
24
|
+
}, {
|
|
25
|
+
operation: 'NdbcService.getActiveStations',
|
|
26
|
+
context: ctx,
|
|
27
|
+
baseDelayMs: 2000,
|
|
28
|
+
maxRetries: 2,
|
|
29
|
+
signal: ctx.signal,
|
|
30
|
+
});
|
|
31
|
+
this.stationCache = { stations, fetchedAt: Date.now() };
|
|
32
|
+
ctx.log.debug('NDBC active stations cached', { count: stations.length });
|
|
33
|
+
return stations;
|
|
34
|
+
}
|
|
35
|
+
/** Pre-warm the active station cache. Called during server setup. */
|
|
36
|
+
async preWarm(ctx) {
|
|
37
|
+
await this.getActiveStations(ctx);
|
|
38
|
+
}
|
|
39
|
+
/** Fetch the most recent observation for a buoy from the realtime2 text feed. */
|
|
40
|
+
async fetchObservation(stationId, ctx) {
|
|
41
|
+
return await withRetry(async () => {
|
|
42
|
+
const url = REALTIME_URL(stationId);
|
|
43
|
+
const response = await fetchWithTimeout(url, 10_000, ctx, {
|
|
44
|
+
signal: ctx.signal,
|
|
45
|
+
});
|
|
46
|
+
const text = await response.text();
|
|
47
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
48
|
+
throw serviceUnavailable('NDBC realtime endpoint returned HTML — likely rate-limited.');
|
|
49
|
+
}
|
|
50
|
+
return this.parseRealtimeText(text, stationId);
|
|
51
|
+
}, {
|
|
52
|
+
operation: `NdbcService.fetchObservation(${stationId})`,
|
|
53
|
+
context: ctx,
|
|
54
|
+
baseDelayMs: 1000,
|
|
55
|
+
maxRetries: 2,
|
|
56
|
+
signal: ctx.signal,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Parse NDBC active stations XML into NdbcStation array. */
|
|
60
|
+
parseActiveStationsXml(xml) {
|
|
61
|
+
const stations = [];
|
|
62
|
+
// Match each <Station ...> element
|
|
63
|
+
const stationRegex = /<Station\s([^>]*?)(?:\/?>)/gi;
|
|
64
|
+
for (let matchResult = stationRegex.exec(xml); matchResult !== null; matchResult = stationRegex.exec(xml)) {
|
|
65
|
+
const attrs = matchResult[1] ?? '';
|
|
66
|
+
const get = (name) => {
|
|
67
|
+
const m = new RegExp(`${name}="([^"]*)"`, 'i').exec(attrs);
|
|
68
|
+
return m ? m[1] : undefined;
|
|
69
|
+
};
|
|
70
|
+
const id = get('ID') ?? get('id');
|
|
71
|
+
const lat = get('lat');
|
|
72
|
+
const lon = get('lon');
|
|
73
|
+
const name = get('name');
|
|
74
|
+
if (!id || lat === undefined || lon === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
const latNum = Number.parseFloat(lat);
|
|
77
|
+
const lonNum = Number.parseFloat(lon);
|
|
78
|
+
if (Number.isNaN(latNum) || Number.isNaN(lonNum))
|
|
79
|
+
continue;
|
|
80
|
+
const stationType = get('type');
|
|
81
|
+
const stationOwner = get('owner');
|
|
82
|
+
stations.push({
|
|
83
|
+
id: id.toUpperCase(),
|
|
84
|
+
name: name ?? id,
|
|
85
|
+
lat: latNum,
|
|
86
|
+
lon: lonNum,
|
|
87
|
+
...(stationType !== undefined && { type: stationType }),
|
|
88
|
+
...(stationOwner !== undefined && { owner: stationOwner }),
|
|
89
|
+
hasMet: get('met') === 'y' || get('met') === '1',
|
|
90
|
+
hasCurrents: get('currents') === 'y' || get('currents') === '1',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return stations;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse NDBC fixed-width realtime2 text file into a NdbcObservation.
|
|
97
|
+
* Line 1: header (# + column names)
|
|
98
|
+
* Line 2: units row (ignored)
|
|
99
|
+
* Lines 3+: data rows most recent first
|
|
100
|
+
* MM = missing sensor value → null
|
|
101
|
+
*/
|
|
102
|
+
parseRealtimeText(text, stationId) {
|
|
103
|
+
const lines = text
|
|
104
|
+
.split('\n')
|
|
105
|
+
.map((l) => l.trim())
|
|
106
|
+
.filter(Boolean);
|
|
107
|
+
// Find the header line
|
|
108
|
+
const headerLine = lines.find((l) => l.startsWith('#'));
|
|
109
|
+
if (!headerLine) {
|
|
110
|
+
throw serviceUnavailable(`NDBC file for ${stationId} has no header row.`);
|
|
111
|
+
}
|
|
112
|
+
const columns = headerLine
|
|
113
|
+
.replace(/^#+\s*/, '')
|
|
114
|
+
.split(/\s+/)
|
|
115
|
+
.map((c) => c.toUpperCase());
|
|
116
|
+
// Find first data row (not starting with #)
|
|
117
|
+
const dataLine = lines.find((l) => !l.startsWith('#'));
|
|
118
|
+
if (!dataLine) {
|
|
119
|
+
throw notFound(`NDBC buoy ${stationId} has no data rows — buoy may be offline.`, {
|
|
120
|
+
stationId,
|
|
121
|
+
reason: 'no_sensor_data',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const values = dataLine.split(/\s+/);
|
|
125
|
+
const get = (col) => {
|
|
126
|
+
const idx = columns.indexOf(col);
|
|
127
|
+
if (idx < 0 || idx >= values.length)
|
|
128
|
+
return null;
|
|
129
|
+
const v = values[idx];
|
|
130
|
+
return v === 'MM' || v === undefined ? null : v;
|
|
131
|
+
};
|
|
132
|
+
const toNum = (col) => {
|
|
133
|
+
const v = get(col);
|
|
134
|
+
if (v === null)
|
|
135
|
+
return null;
|
|
136
|
+
const n = Number.parseFloat(v);
|
|
137
|
+
return Number.isNaN(n) ? null : n;
|
|
138
|
+
};
|
|
139
|
+
// Build ISO timestamp from YY MM DD hh mm columns.
|
|
140
|
+
// NOTE: NDBC header uses lowercase 'mm' for minutes; after uppercasing, the
|
|
141
|
+
// columns array has 'MM' at index 1 (month) AND at index 4 (minute).
|
|
142
|
+
// indexOf returns the first match (month), so we use lastIndexOf for minutes.
|
|
143
|
+
const getMinute = () => {
|
|
144
|
+
const idx = columns.lastIndexOf('MM');
|
|
145
|
+
if (idx < 0 || idx >= values.length)
|
|
146
|
+
return null;
|
|
147
|
+
const v = values[idx];
|
|
148
|
+
return v === 'MM' || v === undefined ? null : v;
|
|
149
|
+
};
|
|
150
|
+
const yy = get('YY') ?? get('#YY');
|
|
151
|
+
const mo = get('MM');
|
|
152
|
+
const dd = get('DD');
|
|
153
|
+
const hh = get('HH');
|
|
154
|
+
const mn = getMinute();
|
|
155
|
+
// NDBC emits 4-digit years in the YY column despite the column name suggesting otherwise.
|
|
156
|
+
// Treat values ≥ 1000 as already-full years; add 2000 only for genuine 2-digit values.
|
|
157
|
+
const year = yy
|
|
158
|
+
? (() => {
|
|
159
|
+
const n = Number.parseInt(yy, 10);
|
|
160
|
+
return n >= 1000 ? n : n < 50 ? 2000 + n : 1900 + n;
|
|
161
|
+
})()
|
|
162
|
+
: new Date().getFullYear();
|
|
163
|
+
const observedAt = yy && mo && dd && hh && mn
|
|
164
|
+
? `${year}-${mo.padStart(2, '0')}-${dd.padStart(2, '0')}T${hh.padStart(2, '0')}:${mn.padStart(2, '0')}:00Z`
|
|
165
|
+
: new Date().toISOString();
|
|
166
|
+
// Check if all sensor fields are MM
|
|
167
|
+
const sensorCols = [
|
|
168
|
+
'WDIR',
|
|
169
|
+
'WSPD',
|
|
170
|
+
'GST',
|
|
171
|
+
'WVHT',
|
|
172
|
+
'DPD',
|
|
173
|
+
'APD',
|
|
174
|
+
'MWD',
|
|
175
|
+
'PRES',
|
|
176
|
+
'ATMP',
|
|
177
|
+
'WTMP',
|
|
178
|
+
'DEWP',
|
|
179
|
+
];
|
|
180
|
+
const allMissing = sensorCols.every((col) => get(col) === null);
|
|
181
|
+
if (allMissing) {
|
|
182
|
+
throw notFound(`NDBC buoy ${stationId} has all sensor fields missing — buoy offline or sensor failure.`, { stationId, reason: 'no_sensor_data' });
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
observedAt,
|
|
186
|
+
windDirectionDeg: toNum('WDIR'),
|
|
187
|
+
windSpeedMs: toNum('WSPD'),
|
|
188
|
+
gustSpeedMs: toNum('GST'),
|
|
189
|
+
waveHeightM: toNum('WVHT'),
|
|
190
|
+
dominantPeriodSec: toNum('DPD'),
|
|
191
|
+
averagePeriodSec: toNum('APD'),
|
|
192
|
+
meanWaveDirectionDeg: toNum('MWD'),
|
|
193
|
+
pressureHpa: toNum('PRES'),
|
|
194
|
+
airTempC: toNum('ATMP'),
|
|
195
|
+
waterTempC: toNum('WTMP'),
|
|
196
|
+
dewPointC: toNum('DEWP'),
|
|
197
|
+
visibilityNmi: toNum('VIS'),
|
|
198
|
+
tideFt: toNum('TIDE'),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// --- Init/accessor pattern ---
|
|
203
|
+
let _service;
|
|
204
|
+
export function initNdbcService() {
|
|
205
|
+
_service = new NdbcService();
|
|
206
|
+
}
|
|
207
|
+
export function getNdbcService() {
|
|
208
|
+
if (!_service) {
|
|
209
|
+
throw new Error('NdbcService not initialized — call initNdbcService() in setup()');
|
|
210
|
+
}
|
|
211
|
+
return _service;
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=ndbc-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ndbc-service.js","sourceRoot":"","sources":["../../../src/services/ndbc/ndbc-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type definitions for the NDBC (National Data Buoy Center) service.
|
|
3
|
+
* @module services/ndbc/types
|
|
4
|
+
*/
|
|
5
|
+
/** A station from the NDBC active stations XML. */
|
|
6
|
+
export interface NdbcStation {
|
|
7
|
+
/** Whether station reports current observations */
|
|
8
|
+
hasCurrents: boolean;
|
|
9
|
+
/** Whether station reports meteorological observations */
|
|
10
|
+
hasMet: boolean;
|
|
11
|
+
id: string;
|
|
12
|
+
lat: number;
|
|
13
|
+
lon: number;
|
|
14
|
+
name: string;
|
|
15
|
+
/** Owner/operator */
|
|
16
|
+
owner?: string;
|
|
17
|
+
/** NDBC station type (buoy, fixed, etc.) */
|
|
18
|
+
type?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parsed NDBC realtime observation.
|
|
22
|
+
* All sensor fields are null when the buoy did not report a value (MM in source).
|
|
23
|
+
*/
|
|
24
|
+
export interface NdbcObservation {
|
|
25
|
+
/** Air temperature °C — null if not reported */
|
|
26
|
+
airTempC: number | null;
|
|
27
|
+
/** Average period seconds — null if not reported */
|
|
28
|
+
averagePeriodSec: number | null;
|
|
29
|
+
/** Dew point temperature °C — null if not reported */
|
|
30
|
+
dewPointC: number | null;
|
|
31
|
+
/** Dominant period seconds — null if not reported */
|
|
32
|
+
dominantPeriodSec: number | null;
|
|
33
|
+
/** Wind gust m/s — null if not reported */
|
|
34
|
+
gustSpeedMs: number | null;
|
|
35
|
+
/** Mean wave direction degrees true — null if not reported */
|
|
36
|
+
meanWaveDirectionDeg: number | null;
|
|
37
|
+
/** ISO timestamp of the observation */
|
|
38
|
+
observedAt: string;
|
|
39
|
+
/** Atmospheric pressure hPa — null if not reported */
|
|
40
|
+
pressureHpa: number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Tide in feet — null if not reported.
|
|
43
|
+
* NOTE: NDBC TIDE is always in feet regardless of other unit preferences.
|
|
44
|
+
*/
|
|
45
|
+
tideFt: number | null;
|
|
46
|
+
/**
|
|
47
|
+
* Visibility in nautical miles — null if not reported.
|
|
48
|
+
* NOTE: NDBC VIS is always in nautical miles regardless of other unit preferences.
|
|
49
|
+
*/
|
|
50
|
+
visibilityNmi: number | null;
|
|
51
|
+
/** Sea surface temperature °C — null if not reported */
|
|
52
|
+
waterTempC: number | null;
|
|
53
|
+
/** Significant wave height m — null if not reported */
|
|
54
|
+
waveHeightM: number | null;
|
|
55
|
+
/** Wind direction degrees true — null if not reported */
|
|
56
|
+
windDirectionDeg: number | null;
|
|
57
|
+
/** Wind speed m/s — null if not reported */
|
|
58
|
+
windSpeedMs: number | null;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/ndbc/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,WAAW,EAAE,OAAO,CAAC;IACrB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sDAAsD;IACtD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qDAAqD;IACrD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,8DAA8D;IAC9D,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;OAGG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wDAAwD;IACxD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,4CAA4C;IAC5C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/ndbc/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|