@ryancardin/noaa-tides-currents-mcp-server 1.0.0 → 2.0.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/README.md +164 -199
- package/dist/client/cache.d.ts +15 -0
- package/dist/client/cache.js +44 -0
- package/dist/client/http.d.ts +28 -0
- package/dist/client/http.js +178 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +28 -0
- package/dist/format/respond.d.ts +25 -0
- package/dist/format/respond.js +40 -0
- package/dist/format/series.d.ts +16 -0
- package/dist/format/series.js +42 -0
- package/dist/format/units.d.ts +16 -0
- package/dist/format/units.js +37 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +78 -8
- package/dist/interfaces/moon.d.ts +4 -4
- package/dist/interfaces/moon.js +58 -17
- package/dist/interfaces/sun.d.ts +4 -4
- package/dist/interfaces/sun.js +94 -25
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.js +89 -0
- package/dist/reference/content.d.ts +10 -0
- package/dist/reference/content.js +202 -0
- package/dist/resources/index.d.ts +7 -0
- package/dist/resources/index.js +73 -0
- package/dist/schemas/common.d.ts +38 -14
- package/dist/schemas/common.js +82 -18
- package/dist/services/data-api.d.ts +81 -0
- package/dist/services/data-api.js +117 -0
- package/dist/services/dpapi.d.ts +55 -0
- package/dist/services/dpapi.js +60 -0
- package/dist/services/metadata-api.d.ts +62 -0
- package/dist/services/metadata-api.js +105 -0
- package/dist/services/moon-phase-service.d.ts +2 -2
- package/dist/services/moon-phase-service.js +23 -25
- package/dist/services/sun-service.d.ts +2 -2
- package/dist/services/sun-service.js +53 -31
- package/dist/tools/astronomy.d.ts +7 -0
- package/dist/tools/astronomy.js +270 -0
- package/dist/tools/currents.d.ts +5 -0
- package/dist/tools/currents.js +168 -0
- package/dist/tools/derived.d.ts +6 -0
- package/dist/tools/derived.js +296 -0
- package/dist/tools/index.d.ts +3 -14
- package/dist/tools/index.js +17 -32
- package/dist/tools/met.d.ts +5 -0
- package/dist/tools/met.js +110 -0
- package/dist/tools/reference.d.ts +6 -0
- package/dist/tools/reference.js +33 -0
- package/dist/tools/station-metadata.d.ts +6 -0
- package/dist/tools/station-metadata.js +208 -0
- package/dist/tools/stations.d.ts +5 -0
- package/dist/tools/stations.js +265 -0
- package/dist/tools/water.d.ts +5 -0
- package/dist/tools/water.js +270 -0
- package/dist/validation/dates.d.ts +50 -0
- package/dist/validation/dates.js +139 -0
- package/package.json +27 -14
- package/.claude/settings.local.json +0 -29
- package/CLAUDE.md +0 -71
- package/Dockerfile +0 -14
- package/smithery.yaml +0 -16
- package/src/index.ts +0 -13
- package/src/interfaces/moon.ts +0 -44
- package/src/interfaces/noaa.ts +0 -130
- package/src/interfaces/parameters.ts +0 -20
- package/src/interfaces/sun.ts +0 -57
- package/src/schemas/common.ts +0 -23
- package/src/schemas/dpapi.ts +0 -99
- package/src/server/config.ts +0 -43
- package/src/server/mcp-server.ts +0 -135
- package/src/services/dpapi-service.ts +0 -187
- package/src/services/moon-phase-service.ts +0 -167
- package/src/services/noaa-parameters-service.ts +0 -139
- package/src/services/noaa-service.ts +0 -171
- package/src/services/sun-service.ts +0 -275
- package/src/tools/derived-product-tools.ts +0 -180
- package/src/tools/index.ts +0 -40
- package/src/tools/moon-tools.ts +0 -79
- package/src/tools/parameter-tools.ts +0 -82
- package/src/tools/station-tools.ts +0 -57
- package/src/tools/sun-tools.ts +0 -120
- package/src/tools/water-tools.ts +0 -166
- package/src/types/moon.ts +0 -27
- package/src/types/sun.ts +0 -51
- package/src/types/suncalc.d.ts +0 -110
- package/test-dpapi.js +0 -0
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived Product API tools: sea level trends & rise projections, extreme
|
|
3
|
+
* water levels, top-ten/peak water levels, high tide flooding.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getExtremeWaterLevels, getHighTideFlooding, getSeaLevelRiseProjections, getSeaLevelTrends, getTopTenWaterLevels, } from "../services/dpapi.js";
|
|
7
|
+
import { READ_ONLY_ANNOTATIONS, ResponseFormatSchema, StationIdSchema, UnitsSchema, } from "../schemas/common.js";
|
|
8
|
+
import { markdownTable, respond, respondError } from "../format/respond.js";
|
|
9
|
+
function firstArray(payload) {
|
|
10
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
11
|
+
if (Array.isArray(value) &&
|
|
12
|
+
value.length > 0 &&
|
|
13
|
+
typeof value[0] === "object") {
|
|
14
|
+
return { key, rows: value };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { rows: [] };
|
|
18
|
+
}
|
|
19
|
+
/** Render whatever record array a DPAPI endpoint returned as a table. */
|
|
20
|
+
function genericMarkdown(title, payload, note) {
|
|
21
|
+
const { rows } = firstArray(payload);
|
|
22
|
+
const lines = [`# ${title}`, ""];
|
|
23
|
+
if (note)
|
|
24
|
+
lines.push(note, "");
|
|
25
|
+
if (rows.length === 0) {
|
|
26
|
+
lines.push("_No records returned. This station may not have this derived product._");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const headers = Object.keys(rows[0]);
|
|
30
|
+
const cell = (value) => {
|
|
31
|
+
if (value === null || value === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
if (typeof value === "object")
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
return String(value);
|
|
36
|
+
};
|
|
37
|
+
lines.push(`**Records**: ${rows.length}`, "", markdownTable(headers, rows.map((r) => headers.map((h) => cell(r[h])))));
|
|
38
|
+
}
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
export function registerDerivedProductTools(server) {
|
|
42
|
+
server.registerTool("noaa_get_sea_level_trends", {
|
|
43
|
+
title: "Get Relative Sea Level Trends",
|
|
44
|
+
description: `Get the long-term relative sea level trend at a NOAA station: trend and error (reported by NOAA in mm/yr or inches/decade — the payload's trendUnits field says which), the observation period (startDate/endDate), and seasonal amplitude.
|
|
45
|
+
|
|
46
|
+
Relative sea level combines ocean rise AND local land movement (subsidence/uplift), so trends vary widely by station (e.g. strongly positive on the Gulf Coast, negative in Alaska). Requires a station with a long water-level record; omit station to list all (affil "US" or "Global" filters the network).`,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
station: StationIdSchema.optional(),
|
|
49
|
+
affil: z
|
|
50
|
+
.enum(["US", "Global"])
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Network filter when listing multiple stations."),
|
|
53
|
+
response_format: ResponseFormatSchema,
|
|
54
|
+
},
|
|
55
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
56
|
+
}, async (params) => {
|
|
57
|
+
try {
|
|
58
|
+
const payload = await getSeaLevelTrends({
|
|
59
|
+
station: params.station,
|
|
60
|
+
affil: params.affil,
|
|
61
|
+
});
|
|
62
|
+
return respond(params.response_format, {
|
|
63
|
+
query: { station: params.station, affil: params.affil },
|
|
64
|
+
...payload,
|
|
65
|
+
}, genericMarkdown(`Sea Level Trends${params.station ? ` — Station ${params.station}` : ""}`, payload, "_Trend units are given per record (trendUnits). Relative sea level includes local land motion._"));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return respondError(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
server.registerTool("noaa_get_sea_level_rise_projections", {
|
|
72
|
+
title: "Get Sea Level Rise Projections",
|
|
73
|
+
description: `Get NOAA's Interagency Sea Level Rise Scenario projections for a station (from the 2022 technical report): projected relative sea level (meters or feet, see units field) per decade through 2150 under the chosen scenario.
|
|
74
|
+
|
|
75
|
+
Scenarios: low, intermediate-low, intermediate, intermediate-high, high, extreme, or all. Use for coastal planning questions ("how much sea level rise is projected at X by 2050?"). Pair with noaa_get_sea_level_trends (observed) and noaa_get_high_tide_flooding (flooding impact).`,
|
|
76
|
+
inputSchema: {
|
|
77
|
+
station: StationIdSchema.optional(),
|
|
78
|
+
scenario: z
|
|
79
|
+
.enum([
|
|
80
|
+
"all",
|
|
81
|
+
"low",
|
|
82
|
+
"intermediate-low",
|
|
83
|
+
"intermediate",
|
|
84
|
+
"intermediate-high",
|
|
85
|
+
"high",
|
|
86
|
+
"extreme",
|
|
87
|
+
])
|
|
88
|
+
.default("intermediate")
|
|
89
|
+
.describe("Emissions/rise scenario from the 2022 Interagency report."),
|
|
90
|
+
projection_year: z
|
|
91
|
+
.number()
|
|
92
|
+
.int()
|
|
93
|
+
.min(2000)
|
|
94
|
+
.max(2150)
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Filter to a single decade year (e.g. 2050)."),
|
|
97
|
+
units: UnitsSchema.optional(),
|
|
98
|
+
response_format: ResponseFormatSchema,
|
|
99
|
+
},
|
|
100
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
101
|
+
}, async (params) => {
|
|
102
|
+
try {
|
|
103
|
+
const payload = await getSeaLevelRiseProjections({
|
|
104
|
+
station: params.station,
|
|
105
|
+
scenario: params.scenario,
|
|
106
|
+
projection_year: params.projection_year,
|
|
107
|
+
units: params.units,
|
|
108
|
+
});
|
|
109
|
+
return respond(params.response_format, {
|
|
110
|
+
query: { station: params.station, scenario: params.scenario },
|
|
111
|
+
...payload,
|
|
112
|
+
}, genericMarkdown(`Sea Level Rise Projections${params.station ? ` — Station ${params.station}` : ""} (${params.scenario})`, payload));
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
return respondError(error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
server.registerTool("noaa_get_extreme_water_levels", {
|
|
119
|
+
title: "Get Extreme Water Levels",
|
|
120
|
+
description: `Get NOAA's extreme water level exceedance statistics for a station: the annual exceedance probability levels (e.g. the 1%-annual-chance "100-year" level) and historical extreme events, computed from the station's verified record.
|
|
121
|
+
|
|
122
|
+
Use for flood risk questions ("what water level has a 1% chance per year at X?"). Levels are relative to the station's datums for the 1983–2001 epoch. Only long-record stations have this product.`,
|
|
123
|
+
inputSchema: {
|
|
124
|
+
station: StationIdSchema,
|
|
125
|
+
units: UnitsSchema,
|
|
126
|
+
extremeType: z
|
|
127
|
+
.enum(["annuals", "monthlies"])
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("Extreme statistics basis: annual (default) or monthly extremes."),
|
|
130
|
+
levelType: z
|
|
131
|
+
.enum(["high", "low"])
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("High extremes (default) or low."),
|
|
134
|
+
response_format: ResponseFormatSchema,
|
|
135
|
+
},
|
|
136
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
137
|
+
}, async (params) => {
|
|
138
|
+
try {
|
|
139
|
+
const payload = await getExtremeWaterLevels({
|
|
140
|
+
station: params.station,
|
|
141
|
+
units: params.units,
|
|
142
|
+
extremeType: params.extremeType,
|
|
143
|
+
levelType: params.levelType,
|
|
144
|
+
});
|
|
145
|
+
return respond(params.response_format, { query: { station: params.station }, ...payload }, genericMarkdown(`Extreme Water Levels — Station ${params.station}`, payload, '_Nested exceedance/event details are in the JSON payload (response_format "json")._'));
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return respondError(error);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
server.registerTool("noaa_get_top_ten_water_levels", {
|
|
152
|
+
title: "Get Top Ten / Peak Water Levels",
|
|
153
|
+
description: `Get a station's highest recorded water levels: analysis "toptenwaterlevels" returns the ten highest events on record (with date, height, causal event name like "Great New England Hurricane", and category); "peakwaterlevels" returns peak event records (optionally filtered by year).
|
|
154
|
+
|
|
155
|
+
Heights are relative to the requested datum (MHHW is typical for flood comparisons). Use for "what's the worst flooding ever recorded at X?".`,
|
|
156
|
+
inputSchema: {
|
|
157
|
+
station: StationIdSchema,
|
|
158
|
+
analysis: z
|
|
159
|
+
.enum(["toptenwaterlevels", "peakwaterlevels"])
|
|
160
|
+
.default("toptenwaterlevels")
|
|
161
|
+
.describe('"toptenwaterlevels" = ten highest on record; "peakwaterlevels" = peak event records.'),
|
|
162
|
+
datum: z
|
|
163
|
+
.enum([
|
|
164
|
+
"STND",
|
|
165
|
+
"MHHW",
|
|
166
|
+
"MHW",
|
|
167
|
+
"MSL",
|
|
168
|
+
"MTL",
|
|
169
|
+
"MLW",
|
|
170
|
+
"MLLW",
|
|
171
|
+
"NAVD",
|
|
172
|
+
"IGLD",
|
|
173
|
+
"LWD",
|
|
174
|
+
])
|
|
175
|
+
.default("MHHW")
|
|
176
|
+
.describe("Reference datum for reported heights (MHHW is standard for flood context)."),
|
|
177
|
+
units: UnitsSchema,
|
|
178
|
+
year: z
|
|
179
|
+
.number()
|
|
180
|
+
.int()
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("For peakwaterlevels: filter to one year."),
|
|
183
|
+
response_format: ResponseFormatSchema,
|
|
184
|
+
},
|
|
185
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
186
|
+
}, async (params) => {
|
|
187
|
+
try {
|
|
188
|
+
const payload = await getTopTenWaterLevels({
|
|
189
|
+
station: params.station,
|
|
190
|
+
analysis: params.analysis,
|
|
191
|
+
units: params.units,
|
|
192
|
+
datum: params.datum,
|
|
193
|
+
year: params.year,
|
|
194
|
+
});
|
|
195
|
+
return respond(params.response_format, {
|
|
196
|
+
query: {
|
|
197
|
+
station: params.station,
|
|
198
|
+
analysis: params.analysis,
|
|
199
|
+
datum: params.datum,
|
|
200
|
+
},
|
|
201
|
+
...payload,
|
|
202
|
+
}, genericMarkdown(`${params.analysis === "toptenwaterlevels" ? "Top Ten Water Levels" : "Peak Water Levels"} — Station ${params.station}`, payload, `_Heights relative to ${params.datum}._`));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return respondError(error);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
server.registerTool("noaa_get_high_tide_flooding", {
|
|
209
|
+
title: "Get High Tide Flooding Data",
|
|
210
|
+
description: `Get NOAA high tide flooding (HTF, "nuisance"/"sunny day" flooding) statistics for a station. Reports:
|
|
211
|
+
|
|
212
|
+
- daily: flood occurrence per day (REQUIRES start_date and end_date, YYYYMMDD)
|
|
213
|
+
- monthly / seasonal / annual: counts of minor/moderate/major flood days per period (filter with year/month/season_months, or range = last N periods)
|
|
214
|
+
- met_year_annual: counts by meteorological year (May–April)
|
|
215
|
+
- annual_outlook: NOAA's projected flood-day outlook for the coming met year
|
|
216
|
+
- projections: decadal flood-day projections through 2100 (filter by decade, flood_threshold)
|
|
217
|
+
- record_days: record flood-day counts
|
|
218
|
+
- likely_scenarios: likely decadal flooding scenarios
|
|
219
|
+
- daily_likelihoods: day-by-day flood likelihood forecasts
|
|
220
|
+
|
|
221
|
+
Use for: "how often does X flood?", trends in nuisance flooding, future flooding projections. Not all stations have HTF products — check the HTFhistorical flag via noaa_get_station_info.`,
|
|
222
|
+
inputSchema: {
|
|
223
|
+
station: StationIdSchema,
|
|
224
|
+
report: z
|
|
225
|
+
.enum([
|
|
226
|
+
"daily",
|
|
227
|
+
"monthly",
|
|
228
|
+
"seasonal",
|
|
229
|
+
"annual",
|
|
230
|
+
"met_year_annual",
|
|
231
|
+
"annual_outlook",
|
|
232
|
+
"projections",
|
|
233
|
+
"record_days",
|
|
234
|
+
"likely_scenarios",
|
|
235
|
+
"daily_likelihoods",
|
|
236
|
+
])
|
|
237
|
+
.default("annual")
|
|
238
|
+
.describe("Which HTF report to retrieve (see tool description)."),
|
|
239
|
+
start_date: z
|
|
240
|
+
.string()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe('YYYYMMDD — required for report "daily".'),
|
|
243
|
+
end_date: z
|
|
244
|
+
.string()
|
|
245
|
+
.optional()
|
|
246
|
+
.describe('YYYYMMDD — required for report "daily".'),
|
|
247
|
+
year: z
|
|
248
|
+
.number()
|
|
249
|
+
.int()
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Filter to a calendar year (monthly/seasonal/annual/record_days)."),
|
|
252
|
+
month: z
|
|
253
|
+
.number()
|
|
254
|
+
.int()
|
|
255
|
+
.min(1)
|
|
256
|
+
.max(12)
|
|
257
|
+
.optional()
|
|
258
|
+
.describe("Filter to a month (monthly report)."),
|
|
259
|
+
range: z
|
|
260
|
+
.number()
|
|
261
|
+
.int()
|
|
262
|
+
.positive()
|
|
263
|
+
.optional()
|
|
264
|
+
.describe("Number of years to cover: with year (or met_year) set, returns year..year+range; without, returns the last N years."),
|
|
265
|
+
season_months: z
|
|
266
|
+
.enum(["DJF", "MAM", "JJA", "SON"])
|
|
267
|
+
.optional()
|
|
268
|
+
.describe("Season for the seasonal report (Dec-Jan-Feb, Mar-Apr-May, ...)."),
|
|
269
|
+
met_year: z
|
|
270
|
+
.number()
|
|
271
|
+
.int()
|
|
272
|
+
.optional()
|
|
273
|
+
.describe("Meteorological year (met_year_annual / annual_outlook)."),
|
|
274
|
+
decade: z
|
|
275
|
+
.number()
|
|
276
|
+
.int()
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("Decade for projections/likely_scenarios (e.g. 2050)."),
|
|
279
|
+
flood_threshold: z
|
|
280
|
+
.enum(["minor", "moderate", "major"])
|
|
281
|
+
.optional()
|
|
282
|
+
.describe("Flood severity threshold for projections/likely_scenarios."),
|
|
283
|
+
response_format: ResponseFormatSchema,
|
|
284
|
+
},
|
|
285
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
286
|
+
}, async (params) => {
|
|
287
|
+
try {
|
|
288
|
+
const { report, response_format, ...rest } = params;
|
|
289
|
+
const payload = await getHighTideFlooding(report, rest);
|
|
290
|
+
return respond(response_format, { query: { station: params.station, report }, ...payload }, genericMarkdown(`High Tide Flooding (${report}) — Station ${params.station}`, payload, "_Counts are flood DAYS exceeding the NOS minor/moderate/major thresholds (majCount/modCount/minCount); nanCount = days with missing data._"));
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return respondError(error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
import { FastMCP } from 'fastmcp';
|
|
2
|
-
import { NoaaService } from '../services/noaa-service.js';
|
|
3
|
-
import { MoonPhaseService } from '../services/moon-phase-service.js';
|
|
4
|
-
import { SunService } from '../services/sun-service.js';
|
|
5
|
-
import { NoaaParametersService } from '../services/noaa-parameters-service.js';
|
|
6
|
-
import { DpapiService } from '../services/dpapi-service.js';
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
2
|
+
* Central registration for all tools.
|
|
9
3
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
moonPhaseService: MoonPhaseService;
|
|
13
|
-
sunService: SunService;
|
|
14
|
-
parametersService: NoaaParametersService;
|
|
15
|
-
dpapiService: DpapiService;
|
|
16
|
-
};
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
export declare function registerAllTools(server: McpServer): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
import { NoaaService } from '../services/noaa-service.js';
|
|
2
|
-
import { MoonPhaseService } from '../services/moon-phase-service.js';
|
|
3
|
-
import { SunService } from '../services/sun-service.js';
|
|
4
|
-
import { NoaaParametersService } from '../services/noaa-parameters-service.js';
|
|
5
|
-
import { DpapiService } from '../services/dpapi-service.js';
|
|
6
|
-
import { registerWaterTools } from './water-tools.js';
|
|
7
|
-
import { registerStationTools } from './station-tools.js';
|
|
8
|
-
import { registerMoonTools } from './moon-tools.js';
|
|
9
|
-
import { registerSunTools } from './sun-tools.js';
|
|
10
|
-
import { registerParameterTools } from './parameter-tools.js';
|
|
11
|
-
import { registerDerivedProductTools } from './derived-product-tools.js';
|
|
12
1
|
/**
|
|
13
|
-
*
|
|
2
|
+
* Central registration for all tools.
|
|
14
3
|
*/
|
|
4
|
+
import { registerWaterTools } from "./water.js";
|
|
5
|
+
import { registerCurrentTools } from "./currents.js";
|
|
6
|
+
import { registerMetTools } from "./met.js";
|
|
7
|
+
import { registerStationTools } from "./stations.js";
|
|
8
|
+
import { registerStationMetadataTools } from "./station-metadata.js";
|
|
9
|
+
import { registerDerivedProductTools } from "./derived.js";
|
|
10
|
+
import { registerAstronomyTools } from "./astronomy.js";
|
|
11
|
+
import { registerReferenceTools } from "./reference.js";
|
|
15
12
|
export function registerAllTools(server) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
registerStationTools(server, noaaService);
|
|
25
|
-
registerMoonTools(server, moonPhaseService);
|
|
26
|
-
registerSunTools(server, sunService);
|
|
27
|
-
registerParameterTools(server, parametersService);
|
|
28
|
-
registerDerivedProductTools(server, dpapiService);
|
|
29
|
-
return {
|
|
30
|
-
noaaService,
|
|
31
|
-
moonPhaseService,
|
|
32
|
-
sunService,
|
|
33
|
-
parametersService,
|
|
34
|
-
dpapiService
|
|
35
|
-
};
|
|
13
|
+
registerWaterTools(server);
|
|
14
|
+
registerCurrentTools(server);
|
|
15
|
+
registerMetTools(server);
|
|
16
|
+
registerStationTools(server);
|
|
17
|
+
registerStationMetadataTools(server);
|
|
18
|
+
registerDerivedProductTools(server);
|
|
19
|
+
registerAstronomyTools(server);
|
|
20
|
+
registerReferenceTools(server);
|
|
36
21
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meteorological and ancillary sensor data tool.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { getMeteorologicalData, } from "../services/data-api.js";
|
|
6
|
+
import { dateFields, READ_ONLY_ANNOTATIONS, ResponseFormatSchema, StationIdSchema, TimeZoneSchema, UnitsSchema, } from "../schemas/common.js";
|
|
7
|
+
import { respond, respondError } from "../format/respond.js";
|
|
8
|
+
import { seriesMarkdown, timeZoneLabel } from "../format/series.js";
|
|
9
|
+
import { FLAG_LEGENDS, unitLabel } from "../format/units.js";
|
|
10
|
+
const MET_KIND = {
|
|
11
|
+
air_temperature: "air_temperature",
|
|
12
|
+
water_temperature: "water_temperature",
|
|
13
|
+
wind: "wind",
|
|
14
|
+
air_pressure: "air_pressure",
|
|
15
|
+
air_gap: "air_gap",
|
|
16
|
+
conductivity: "conductivity",
|
|
17
|
+
visibility: "visibility",
|
|
18
|
+
humidity: "humidity",
|
|
19
|
+
salinity: "salinity",
|
|
20
|
+
};
|
|
21
|
+
export function registerMetTools(server) {
|
|
22
|
+
server.registerTool("noaa_get_meteorological_data", {
|
|
23
|
+
title: "Get Meteorological & Sensor Data",
|
|
24
|
+
description: `Get observed meteorological/oceanographic sensor data from a NOAA station.
|
|
25
|
+
|
|
26
|
+
Products: air_temperature, water_temperature, wind (speed/gust/direction), air_pressure, air_gap (bridge clearance to water surface), conductivity, visibility, humidity, salinity.
|
|
27
|
+
|
|
28
|
+
Units by system: temps °F/°C; wind knots (english) or m/s (metric); air_gap feet/meters; visibility nautical miles/kilometers; air_pressure always millibars; salinity always PSU. Max span ~31 days per request; interval "6" (6-minute, default) or "h" (hourly).
|
|
29
|
+
|
|
30
|
+
Not every station has every sensor — check with noaa_get_station_info (expand ["sensors"]). Water-level data is served by noaa_get_water_levels, not this tool.`,
|
|
31
|
+
inputSchema: {
|
|
32
|
+
station: StationIdSchema,
|
|
33
|
+
product: z
|
|
34
|
+
.enum([
|
|
35
|
+
"air_temperature",
|
|
36
|
+
"water_temperature",
|
|
37
|
+
"wind",
|
|
38
|
+
"air_pressure",
|
|
39
|
+
"air_gap",
|
|
40
|
+
"conductivity",
|
|
41
|
+
"visibility",
|
|
42
|
+
"humidity",
|
|
43
|
+
"salinity",
|
|
44
|
+
])
|
|
45
|
+
.describe("Sensor product to retrieve."),
|
|
46
|
+
interval: z
|
|
47
|
+
.enum(["6", "h"])
|
|
48
|
+
.default("6")
|
|
49
|
+
.describe('"6" = 6-minute observations (default), "h" = hourly.'),
|
|
50
|
+
units: UnitsSchema,
|
|
51
|
+
time_zone: TimeZoneSchema,
|
|
52
|
+
...dateFields,
|
|
53
|
+
response_format: ResponseFormatSchema,
|
|
54
|
+
},
|
|
55
|
+
annotations: READ_ONLY_ANNOTATIONS,
|
|
56
|
+
}, async (params) => {
|
|
57
|
+
try {
|
|
58
|
+
const response = await getMeteorologicalData(params);
|
|
59
|
+
const data = response.data ?? [];
|
|
60
|
+
const kind = MET_KIND[params.product];
|
|
61
|
+
const valueLabel = unitLabel(kind, params.units);
|
|
62
|
+
const structured = {
|
|
63
|
+
station: params.station,
|
|
64
|
+
product: params.product,
|
|
65
|
+
units: params.units,
|
|
66
|
+
units_label: valueLabel,
|
|
67
|
+
time_zone: params.time_zone,
|
|
68
|
+
count: data.length,
|
|
69
|
+
data,
|
|
70
|
+
};
|
|
71
|
+
let headers;
|
|
72
|
+
let rows;
|
|
73
|
+
let legend;
|
|
74
|
+
if (params.product === "wind") {
|
|
75
|
+
headers = [
|
|
76
|
+
"Time",
|
|
77
|
+
`Speed (${valueLabel})`,
|
|
78
|
+
`Gust (${valueLabel})`,
|
|
79
|
+
"Direction (°T)",
|
|
80
|
+
"Compass",
|
|
81
|
+
"Flags",
|
|
82
|
+
];
|
|
83
|
+
rows = data.map((d) => [d.t, d.s, d.g, d.d, d.dr, d.f]);
|
|
84
|
+
legend = FLAG_LEGENDS.wind;
|
|
85
|
+
}
|
|
86
|
+
else if (params.product === "salinity") {
|
|
87
|
+
headers = ["Time", "Salinity (PSU)", "Specific gravity"];
|
|
88
|
+
rows = data.map((d) => [d.t, d.s, d.g]);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
headers = ["Time", `Value (${valueLabel})`, "Flags"];
|
|
92
|
+
rows = data.map((d) => [d.t, d.v, d.f]);
|
|
93
|
+
legend =
|
|
94
|
+
params.product === "air_gap"
|
|
95
|
+
? FLAG_LEGENDS.air_gap
|
|
96
|
+
: FLAG_LEGENDS.met;
|
|
97
|
+
}
|
|
98
|
+
const markdown = seriesMarkdown({
|
|
99
|
+
title: `Meteorological Data — ${params.product}`,
|
|
100
|
+
station: params.station,
|
|
101
|
+
unitsLabel: valueLabel,
|
|
102
|
+
timeZone: timeZoneLabel(params.time_zone),
|
|
103
|
+
}, headers, rows, legend);
|
|
104
|
+
return respond(params.response_format, structured, markdown);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return respondError(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference guide tool — curated NOAA parameter documentation so agents can
|
|
3
|
+
* self-serve the nuances (datums, units, limits) without web searching.
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
export declare function registerReferenceTools(server: McpServer): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference guide tool — curated NOAA parameter documentation so agents can
|
|
3
|
+
* self-serve the nuances (datums, units, limits) without web searching.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { REFERENCE_CONTENT, REFERENCE_SUMMARIES, REFERENCE_TOPICS, } from "../reference/content.js";
|
|
7
|
+
import { respondError } from "../format/respond.js";
|
|
8
|
+
import { LOCAL_COMPUTE_ANNOTATIONS } from "../schemas/common.js";
|
|
9
|
+
export function registerReferenceTools(server) {
|
|
10
|
+
server.registerTool("noaa_get_reference_guide", {
|
|
11
|
+
title: "Get NOAA API Reference Guide",
|
|
12
|
+
description: `Get curated reference documentation for NOAA CO-OPS concepts and parameters. Topics:
|
|
13
|
+
|
|
14
|
+
${REFERENCE_TOPICS.map((t) => `- ${t}: ${REFERENCE_SUMMARIES[t]}`).join("\n")}
|
|
15
|
+
|
|
16
|
+
Consult this before constructing unusual requests — especially "datums" (which vertical reference to use and station applicability), "units" (english/metric asymmetries like cm/s current speeds), and "data_limits" (maximum date spans per product).`,
|
|
17
|
+
inputSchema: {
|
|
18
|
+
topic: z
|
|
19
|
+
.enum(REFERENCE_TOPICS)
|
|
20
|
+
.describe("Reference topic to retrieve."),
|
|
21
|
+
},
|
|
22
|
+
annotations: LOCAL_COMPUTE_ANNOTATIONS,
|
|
23
|
+
}, async ({ topic }) => {
|
|
24
|
+
try {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: REFERENCE_CONTENT[topic] }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return respondError(error);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|