@ryancardin/noaa-tides-currents-mcp-server 1.0.0 → 2.0.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.
Files changed (88) hide show
  1. package/README.md +164 -199
  2. package/dist/client/cache.d.ts +15 -0
  3. package/dist/client/cache.js +44 -0
  4. package/dist/client/http.d.ts +28 -0
  5. package/dist/client/http.js +178 -0
  6. package/dist/constants.d.ts +28 -0
  7. package/dist/constants.js +28 -0
  8. package/dist/format/respond.d.ts +25 -0
  9. package/dist/format/respond.js +40 -0
  10. package/dist/format/series.d.ts +16 -0
  11. package/dist/format/series.js +42 -0
  12. package/dist/format/units.d.ts +16 -0
  13. package/dist/format/units.js +37 -0
  14. package/dist/index.d.ts +14 -0
  15. package/dist/index.js +78 -8
  16. package/dist/interfaces/moon.d.ts +4 -4
  17. package/dist/interfaces/moon.js +58 -17
  18. package/dist/interfaces/sun.d.ts +4 -4
  19. package/dist/interfaces/sun.js +94 -25
  20. package/dist/prompts/index.d.ts +6 -0
  21. package/dist/prompts/index.js +89 -0
  22. package/dist/reference/content.d.ts +10 -0
  23. package/dist/reference/content.js +202 -0
  24. package/dist/resources/index.d.ts +7 -0
  25. package/dist/resources/index.js +73 -0
  26. package/dist/schemas/common.d.ts +38 -14
  27. package/dist/schemas/common.js +82 -18
  28. package/dist/services/data-api.d.ts +81 -0
  29. package/dist/services/data-api.js +117 -0
  30. package/dist/services/dpapi.d.ts +55 -0
  31. package/dist/services/dpapi.js +60 -0
  32. package/dist/services/metadata-api.d.ts +62 -0
  33. package/dist/services/metadata-api.js +105 -0
  34. package/dist/services/moon-phase-service.d.ts +2 -2
  35. package/dist/services/moon-phase-service.js +23 -25
  36. package/dist/services/sun-service.d.ts +2 -2
  37. package/dist/services/sun-service.js +53 -31
  38. package/dist/tools/astronomy.d.ts +7 -0
  39. package/dist/tools/astronomy.js +270 -0
  40. package/dist/tools/currents.d.ts +5 -0
  41. package/dist/tools/currents.js +168 -0
  42. package/dist/tools/derived.d.ts +6 -0
  43. package/dist/tools/derived.js +296 -0
  44. package/dist/tools/index.d.ts +3 -14
  45. package/dist/tools/index.js +17 -32
  46. package/dist/tools/met.d.ts +5 -0
  47. package/dist/tools/met.js +110 -0
  48. package/dist/tools/reference.d.ts +6 -0
  49. package/dist/tools/reference.js +33 -0
  50. package/dist/tools/station-metadata.d.ts +6 -0
  51. package/dist/tools/station-metadata.js +208 -0
  52. package/dist/tools/stations.d.ts +5 -0
  53. package/dist/tools/stations.js +265 -0
  54. package/dist/tools/water.d.ts +5 -0
  55. package/dist/tools/water.js +270 -0
  56. package/dist/validation/dates.d.ts +50 -0
  57. package/dist/validation/dates.js +139 -0
  58. package/package.json +27 -14
  59. package/.claude/settings.local.json +0 -29
  60. package/CLAUDE.md +0 -71
  61. package/Dockerfile +0 -14
  62. package/smithery.yaml +0 -16
  63. package/src/index.ts +0 -13
  64. package/src/interfaces/moon.ts +0 -44
  65. package/src/interfaces/noaa.ts +0 -130
  66. package/src/interfaces/parameters.ts +0 -20
  67. package/src/interfaces/sun.ts +0 -57
  68. package/src/schemas/common.ts +0 -23
  69. package/src/schemas/dpapi.ts +0 -99
  70. package/src/server/config.ts +0 -43
  71. package/src/server/mcp-server.ts +0 -135
  72. package/src/services/dpapi-service.ts +0 -187
  73. package/src/services/moon-phase-service.ts +0 -167
  74. package/src/services/noaa-parameters-service.ts +0 -139
  75. package/src/services/noaa-service.ts +0 -171
  76. package/src/services/sun-service.ts +0 -275
  77. package/src/tools/derived-product-tools.ts +0 -180
  78. package/src/tools/index.ts +0 -40
  79. package/src/tools/moon-tools.ts +0 -79
  80. package/src/tools/parameter-tools.ts +0 -82
  81. package/src/tools/station-tools.ts +0 -57
  82. package/src/tools/sun-tools.ts +0 -120
  83. package/src/tools/water-tools.ts +0 -166
  84. package/src/types/moon.ts +0 -27
  85. package/src/types/sun.ts +0 -51
  86. package/src/types/suncalc.d.ts +0 -110
  87. package/test-dpapi.js +0 -0
  88. package/tsconfig.json +0 -15
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Station scientific metadata tools: datums, harmonic constituents,
3
+ * prediction offsets.
4
+ */
5
+ import { z } from "zod";
6
+ import { extractList, getStationResource } from "../services/metadata-api.js";
7
+ import { READ_ONLY_ANNOTATIONS, ResponseFormatSchema, StationIdSchema, UnitsSchema, } from "../schemas/common.js";
8
+ import { markdownTable, respond, respondError } from "../format/respond.js";
9
+ import { unitLabel } from "../format/units.js";
10
+ export function registerStationMetadataTools(server) {
11
+ server.registerTool("noaa_get_station_datums", {
12
+ title: "Get Station Datums",
13
+ description: `Get the accepted tidal datum values for a station: the elevations of MHHW, MHW, MTL, MSL, DTL, MLW, MLLW, STND, NAVD88 (where computed), plus GT (great diurnal range), MN (mean range), LAT/HAT (lowest/highest astronomical tide), and the station's historical extreme min/max water levels with dates.
14
+
15
+ epoch "current" returns the present National Tidal Datum Epoch (1983–2001) values; "superseded" returns the prior epoch's values (useful for historical comparisons — not all stations have one).
16
+
17
+ All values share one reference zero (the station datum), so datum-to-datum conversion is subtraction: height_above_MLLW = height_above_MSL + (MSL − MLLW). Use this tool to (1) check which datums a station supports before requesting data, and (2) convert heights between datums.`,
18
+ inputSchema: {
19
+ station: StationIdSchema,
20
+ epoch: z
21
+ .enum(["current", "superseded"])
22
+ .default("current")
23
+ .describe('"current" = present NTDE (1983–2001); "superseded" = prior epoch values.'),
24
+ units: UnitsSchema,
25
+ response_format: ResponseFormatSchema,
26
+ },
27
+ annotations: READ_ONLY_ANNOTATIONS,
28
+ }, async (params) => {
29
+ try {
30
+ const resource = params.epoch === "superseded" ? "supersededdatums" : "datums";
31
+ const payload = await getStationResource(params.station, resource, {
32
+ units: params.units,
33
+ });
34
+ const datums = extractList(payload, "datums", "datumList");
35
+ const heightUnits = unitLabel("water_level", params.units);
36
+ const structured = {
37
+ station: params.station,
38
+ epoch_requested: params.epoch,
39
+ epoch: payload.epoch,
40
+ units_label: heightUnits,
41
+ orthometric_datum: payload.OrthometricDatum,
42
+ datums,
43
+ LAT: payload.LAT,
44
+ HAT: payload.HAT,
45
+ historic_min: payload.min,
46
+ historic_min_date: payload.mindate,
47
+ historic_max: payload.max,
48
+ historic_max_date: payload.maxdate,
49
+ };
50
+ const lines = [
51
+ `# Tidal Datums — Station ${params.station}`,
52
+ "",
53
+ `**Epoch**: ${String(payload.epoch ?? "n/a")} (${params.epoch}) · **Units**: ${heightUnits} above station datum`,
54
+ "",
55
+ datums.length === 0
56
+ ? "_No datums published for this station and epoch._"
57
+ : markdownTable(["Datum", "Description", `Value (${heightUnits})`], datums.map((d) => [d.name, d.description, d.value])),
58
+ "",
59
+ `- **HAT** (highest astronomical tide): ${String(payload.HAT ?? "—")} · **LAT**: ${String(payload.LAT ?? "—")}`,
60
+ `- **Historic max**: ${String(payload.max ?? "—")} on ${String(payload.maxdate ?? "—")} · **Historic min**: ${String(payload.min ?? "—")} on ${String(payload.mindate ?? "—")}`,
61
+ ];
62
+ return respond(params.response_format, structured, lines.join("\n"));
63
+ }
64
+ catch (error) {
65
+ return respondError(error);
66
+ }
67
+ });
68
+ server.registerTool("noaa_get_harmonic_constituents", {
69
+ title: "Get Harmonic Constituents",
70
+ description: `Get the harmonic constituents NOAA uses to compute tide or current predictions at a station — the amplitude, phase, and angular speed of each tidal constituent (M2, S2, N2, K1, O1, ...).
71
+
72
+ For water-level stations: amplitude (feet/meters), phase_GMT and phase_local (degrees), speed (degrees/hour). For current stations (alphanumeric IDs) constituents are current ellipses (major/minor amplitudes and phases per depth bin — pass bin to filter).
73
+
74
+ Use for: building custom tide computations, checking a station's dominant constituents (M2 amplitude indicates semidiurnal range), verifying whether a station is harmonically predicted at all. Only reference (R) stations have constituents — subordinate stations use offsets (noaa_get_prediction_offsets).`,
75
+ inputSchema: {
76
+ station: StationIdSchema,
77
+ bin: z
78
+ .number()
79
+ .int()
80
+ .min(1)
81
+ .optional()
82
+ .describe("For current stations: restrict to one depth bin."),
83
+ units: UnitsSchema,
84
+ response_format: ResponseFormatSchema,
85
+ },
86
+ annotations: READ_ONLY_ANNOTATIONS,
87
+ }, async (params) => {
88
+ try {
89
+ const payload = await getStationResource(params.station, "harcon", {
90
+ units: params.units,
91
+ bin: params.bin,
92
+ });
93
+ const constituents = extractList(payload, "HarmonicConstituents", "harmonicConstituents", "harconList");
94
+ const isCurrentStation = constituents[0]?.majorAmplitude !== undefined;
95
+ // Water-level harcon amplitudes are heights (feet/meters). Current
96
+ // harcon reports a compound units string like "meters, centimeters/second"
97
+ // (bin depth, amplitude) — prefer NOAA's own label when present.
98
+ const amplitudeUnits = typeof payload.units === "string" && payload.units
99
+ ? payload.units
100
+ : unitLabel(isCurrentStation ? "currents" : "water_level", params.units);
101
+ const structured = {
102
+ station: params.station,
103
+ bin: params.bin,
104
+ amplitude_units: amplitudeUnits,
105
+ count: constituents.length,
106
+ constituents,
107
+ };
108
+ const markdown = [
109
+ `# Harmonic Constituents — Station ${params.station}${params.bin ? ` (bin ${params.bin})` : ""}`,
110
+ "",
111
+ `**Count**: ${constituents.length} · **Amplitude units**: ${amplitudeUnits} · phases in degrees, speed in degrees/hour`,
112
+ "",
113
+ constituents.length === 0
114
+ ? "_No harmonic constituents published — this may be a subordinate station (see noaa_get_prediction_offsets)._"
115
+ : isCurrentStation
116
+ ? markdownTable([
117
+ "#",
118
+ "Name",
119
+ "Major amp",
120
+ "Major phase (GMT)",
121
+ "Minor amp",
122
+ "Speed",
123
+ "Bin",
124
+ ], constituents.map((c) => [
125
+ String(c.constNum ?? c.number ?? ""),
126
+ String(c.constituentName ?? c.name ?? ""),
127
+ String(c.majorAmplitude ?? ""),
128
+ String(c.majorPhaseGMT ?? ""),
129
+ String(c.minorAmplitude ?? ""),
130
+ String(c.majorMeanSpeed ?? ""),
131
+ String(c.binNbr ?? ""),
132
+ ]))
133
+ : markdownTable([
134
+ "#",
135
+ "Name",
136
+ "Description",
137
+ `Amplitude (${amplitudeUnits})`,
138
+ "Phase GMT (°)",
139
+ "Phase local (°)",
140
+ "Speed (°/hr)",
141
+ ], constituents.map((c) => [
142
+ String(c.number ?? ""),
143
+ String(c.name ?? ""),
144
+ String(c.description ?? ""),
145
+ String(c.amplitude ?? ""),
146
+ String(c.phase_GMT ?? ""),
147
+ String(c.phase_local ?? ""),
148
+ String(c.speed ?? ""),
149
+ ])),
150
+ ].join("\n");
151
+ return respond(params.response_format, structured, markdown);
152
+ }
153
+ catch (error) {
154
+ return respondError(error);
155
+ }
156
+ });
157
+ server.registerTool("noaa_get_prediction_offsets", {
158
+ title: "Get Prediction Offsets (Subordinate Stations)",
159
+ description: `Get the offsets a subordinate (type "S") prediction station applies to its reference station's predictions.
160
+
161
+ kind "tide": returns refStationId, time offsets for high/low tide (minutes), height offsets for high/low tide, and whether the height adjustment is a ratio (multiplied) or fixed value — this is how NOAA derives subordinate-station tide times from the reference harmonic station.
162
+
163
+ kind "current": returns refStationId/bin, mean flood/ebb directions, and time adjustments (minutes) for max flood, slack-before-ebb, max ebb, slack-before-flood plus amplitude ratios. Note: current prediction offsets are indexed per bin — pass the station_bin_suffix ID form (e.g. "ACT0091_1") if the plain ID returns nothing.
164
+
165
+ Reference (R) stations return empty/null offsets — they don't need any.`,
166
+ inputSchema: {
167
+ station: StationIdSchema,
168
+ kind: z
169
+ .enum(["tide", "current"])
170
+ .default("tide")
171
+ .describe('"tide" = tidepredoffsets; "current" = currentpredictionoffsets.'),
172
+ response_format: ResponseFormatSchema,
173
+ },
174
+ annotations: READ_ONLY_ANNOTATIONS,
175
+ }, async (params) => {
176
+ try {
177
+ const resource = params.kind === "tide"
178
+ ? "tidepredoffsets"
179
+ : "currentpredictionoffsets";
180
+ const payload = await getStationResource(params.station, resource);
181
+ const structured = {
182
+ station: params.station,
183
+ kind: params.kind,
184
+ offsets: payload,
185
+ };
186
+ const lines = [
187
+ `# Prediction Offsets (${params.kind}) — Station ${params.station}`,
188
+ "",
189
+ ];
190
+ if (params.kind === "tide") {
191
+ const p = payload;
192
+ if (!p.refStationId) {
193
+ lines.push("_No offsets — this is likely a reference (R) station with its own harmonic constituents, or has no published offsets._");
194
+ }
195
+ else {
196
+ lines.push(`- **Reference station**: ${String(p.refStationId)}`, `- **High tide**: time offset ${String(p.timeOffsetHighTide ?? "—")} min, height offset ${String(p.heightOffsetHighTide ?? "—")}`, `- **Low tide**: time offset ${String(p.timeOffsetLowTide ?? "—")} min, height offset ${String(p.heightOffsetLowTide ?? "—")}`, `- **Height adjustment type**: ${String(p.heightAdjustedType ?? "—")} (R = ratio/multiplier, F = fixed additive)`);
197
+ }
198
+ }
199
+ else {
200
+ lines.push("```json", JSON.stringify(payload, null, 2), "```");
201
+ }
202
+ return respond(params.response_format, structured, lines.join("\n"));
203
+ }
204
+ catch (error) {
205
+ return respondError(error);
206
+ }
207
+ });
208
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Station discovery and overview tools (Metadata API).
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function registerStationTools(server: McpServer): void;
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Station discovery and overview tools (Metadata API).
3
+ */
4
+ import { z } from "zod";
5
+ import { findNearestStations, getStationInfo, searchStations, STATION_TYPES, } from "../services/metadata-api.js";
6
+ import { LatitudeSchema, LongitudeSchema, READ_ONLY_ANNOTATIONS, ResponseFormatSchema, StationIdSchema, UnitsSchema, } from "../schemas/common.js";
7
+ import { markdownTable, respond, respondError } from "../format/respond.js";
8
+ const StationTypeSchema = z
9
+ .enum(STATION_TYPES)
10
+ .describe('Station capability filter. Common: "waterlevels" (active water level), "tidepredictions", "currents" (observed currents), "currentpredictions", "met" (weather sensors). Full list via noaa_get_reference_guide topic "station_types".');
11
+ function summarize(s) {
12
+ return {
13
+ id: s.id,
14
+ name: s.name,
15
+ state: s.state ?? undefined,
16
+ lat: s.lat,
17
+ lng: s.lng,
18
+ tide_type: s.tideType || undefined,
19
+ great_lakes: s.greatlakes || undefined,
20
+ prediction_type: s.type === "R"
21
+ ? "reference (harmonic)"
22
+ : s.type === "S"
23
+ ? "subordinate (offsets)"
24
+ : undefined,
25
+ reference_id: s.reference_id || undefined,
26
+ ports_code: s.portscode || undefined,
27
+ };
28
+ }
29
+ export function registerStationTools(server) {
30
+ server.registerTool("noaa_search_stations", {
31
+ title: "Search NOAA Stations",
32
+ description: `Search the NOAA CO-OPS station directory by capability type, name, and/or state.
33
+
34
+ Filter with:
35
+ - type: what the station does (waterlevels, tidepredictions, currents, currentpredictions, met, ...) — pick the type matching the data you plan to request.
36
+ - name: case-insensitive substring ("San Francisco", "Boston").
37
+ - state: two-letter code ("CA", "MA").
38
+
39
+ Returns id, name, location, tide type, Great Lakes flag, and for prediction stations whether they are reference (R, harmonic) or subordinate (S, offset-based — hilo predictions only). Results are paginated (limit/offset). For proximity search by coordinates use noaa_find_nearest_stations instead.`,
40
+ inputSchema: {
41
+ type: StationTypeSchema.optional(),
42
+ name: z
43
+ .string()
44
+ .min(1)
45
+ .optional()
46
+ .describe("Case-insensitive substring of the station name."),
47
+ state: z
48
+ .string()
49
+ .length(2)
50
+ .optional()
51
+ .describe('Two-letter US state/territory code, e.g. "CA".'),
52
+ limit: z
53
+ .number()
54
+ .int()
55
+ .min(1)
56
+ .max(100)
57
+ .default(25)
58
+ .describe("Maximum stations to return."),
59
+ offset: z
60
+ .number()
61
+ .int()
62
+ .min(0)
63
+ .default(0)
64
+ .describe("Pagination offset."),
65
+ response_format: ResponseFormatSchema,
66
+ },
67
+ annotations: READ_ONLY_ANNOTATIONS,
68
+ }, async (params) => {
69
+ try {
70
+ const matches = await searchStations({
71
+ type: params.type,
72
+ name: params.name,
73
+ state: params.state,
74
+ units: "english",
75
+ });
76
+ const page = matches.slice(params.offset, params.offset + params.limit);
77
+ const hasMore = params.offset + page.length < matches.length;
78
+ const structured = {
79
+ total: matches.length,
80
+ count: page.length,
81
+ offset: params.offset,
82
+ has_more: hasMore,
83
+ ...(hasMore ? { next_offset: params.offset + page.length } : {}),
84
+ stations: page.map(summarize),
85
+ };
86
+ const filterDesc = [
87
+ params.type && `type=${params.type}`,
88
+ params.name && `name~"${params.name}"`,
89
+ params.state && `state=${params.state}`,
90
+ ]
91
+ .filter(Boolean)
92
+ .join(", ");
93
+ const markdown = [
94
+ `# Station Search${filterDesc ? ` (${filterDesc})` : ""}`,
95
+ "",
96
+ `${matches.length} match(es); showing ${page.length} from offset ${params.offset}.${hasMore ? ` More available — call again with offset=${params.offset + page.length}.` : ""}`,
97
+ "",
98
+ markdownTable(["ID", "Name", "State", "Lat", "Lon", "Tide type", "Notes"], page.map((s) => [
99
+ s.id,
100
+ s.name,
101
+ s.state,
102
+ s.lat,
103
+ s.lng,
104
+ s.tideType,
105
+ [
106
+ s.greatlakes ? "Great Lakes" : null,
107
+ s.type === "S"
108
+ ? `subordinate of ${s.reference_id ?? "?"}`
109
+ : null,
110
+ s.type === "R" ? "reference" : null,
111
+ ]
112
+ .filter(Boolean)
113
+ .join("; "),
114
+ ])),
115
+ ].join("\n");
116
+ return respond(params.response_format, structured, markdown);
117
+ }
118
+ catch (error) {
119
+ return respondError(error);
120
+ }
121
+ });
122
+ server.registerTool("noaa_find_nearest_stations", {
123
+ title: "Find Nearest NOAA Stations",
124
+ description: `Find the NOAA stations closest to a latitude/longitude point, sorted by distance.
125
+
126
+ Use this to answer "what's the tide station near <place>?" — geocode the place to coordinates first, then call this. Filter by type to match the data you need (e.g. type "tidepredictions" before calling noaa_get_tide_predictions, "currents" before noaa_get_currents — current stations have different IDs than water-level stations).
127
+
128
+ Distance is computed great-circle (Haversine) and reported in both km and miles. (NOAA's API has no native coordinate search; this tool maintains a cached station directory.)`,
129
+ inputSchema: {
130
+ latitude: LatitudeSchema,
131
+ longitude: LongitudeSchema,
132
+ type: StationTypeSchema.optional(),
133
+ limit: z
134
+ .number()
135
+ .int()
136
+ .min(1)
137
+ .max(50)
138
+ .default(10)
139
+ .describe("Number of stations to return."),
140
+ max_distance_km: z
141
+ .number()
142
+ .positive()
143
+ .optional()
144
+ .describe("Optional cutoff radius in kilometers."),
145
+ response_format: ResponseFormatSchema,
146
+ },
147
+ annotations: READ_ONLY_ANNOTATIONS,
148
+ }, async (params) => {
149
+ try {
150
+ const nearest = await findNearestStations(params.latitude, params.longitude, params.type, params.limit, params.max_distance_km);
151
+ const structured = {
152
+ origin: { latitude: params.latitude, longitude: params.longitude },
153
+ type: params.type,
154
+ count: nearest.length,
155
+ stations: nearest.map((s) => ({
156
+ ...summarize(s),
157
+ distance_km: Number(s.distance_km.toFixed(2)),
158
+ distance_mi: Number(s.distance_mi.toFixed(2)),
159
+ })),
160
+ };
161
+ const markdown = [
162
+ `# Nearest Stations to (${params.latitude}, ${params.longitude})${params.type ? ` — type ${params.type}` : ""}`,
163
+ "",
164
+ nearest.length === 0
165
+ ? "_No stations found within the given constraints. Try removing max_distance_km or the type filter._"
166
+ : markdownTable([
167
+ "ID",
168
+ "Name",
169
+ "State",
170
+ "Distance (km)",
171
+ "Distance (mi)",
172
+ "Tide type",
173
+ "Notes",
174
+ ], nearest.map((s) => [
175
+ s.id,
176
+ s.name,
177
+ s.state,
178
+ s.distance_km.toFixed(1),
179
+ s.distance_mi.toFixed(1),
180
+ s.tideType,
181
+ s.type === "S"
182
+ ? `subordinate of ${s.reference_id ?? "?"}`
183
+ : s.greatlakes
184
+ ? "Great Lakes"
185
+ : "",
186
+ ])),
187
+ ].join("\n");
188
+ return respond(params.response_format, structured, markdown);
189
+ }
190
+ catch (error) {
191
+ return respondError(error);
192
+ }
193
+ });
194
+ server.registerTool("noaa_get_station_info", {
195
+ title: "Get Station Info",
196
+ description: `Get a NOAA station's full metadata record: location, state, time zone, tide type, Great Lakes flag, capability flags, and links to available sub-resources.
197
+
198
+ Optionally expand sub-resources inline via the "expand" list:
199
+ - details (established/removed dates), sensors (installed instruments + elevations), floodlevels (NOS/NWS minor/moderate/major flood thresholds), benchmarks, products (available data page links), notices, disclaimers — for water-level stations
200
+ - bins (ADCP depth bins), deployments — for current stations (alphanumeric IDs)
201
+
202
+ Use this before requesting data to confirm what the station actually collects. For datum values use noaa_get_station_datums; for harmonic constituents use noaa_get_harmonic_constituents.`,
203
+ inputSchema: {
204
+ station: StationIdSchema,
205
+ expand: z
206
+ .array(z.enum([
207
+ "details",
208
+ "sensors",
209
+ "floodlevels",
210
+ "benchmarks",
211
+ "products",
212
+ "notices",
213
+ "disclaimers",
214
+ "bins",
215
+ "deployments",
216
+ ]))
217
+ .optional()
218
+ .describe("Sub-resources to embed inline (availability varies by station type)."),
219
+ units: UnitsSchema,
220
+ response_format: ResponseFormatSchema,
221
+ },
222
+ annotations: READ_ONLY_ANNOTATIONS,
223
+ }, async (params) => {
224
+ try {
225
+ const payload = await getStationInfo(params.station, params.expand, params.units);
226
+ const stations = payload.stations;
227
+ const info = stations?.[0] ?? payload;
228
+ const structured = {
229
+ station: params.station,
230
+ units: params.units,
231
+ info,
232
+ };
233
+ const lines = [
234
+ `# Station ${params.station} — ${String(info.name ?? "")}`,
235
+ "",
236
+ ];
237
+ const fact = (label, value) => {
238
+ if (value !== undefined && value !== null && value !== "") {
239
+ lines.push(`- **${label}**: ${String(value)}`);
240
+ }
241
+ };
242
+ fact("Location", `${info.lat}, ${info.lng}`);
243
+ fact("State", info.state);
244
+ fact("Time zone", info.timezone);
245
+ fact("Tide type", info.tideType);
246
+ fact("Tidal", info.tidal);
247
+ fact("Great Lakes", info.greatlakes);
248
+ fact("Affiliations", info.affiliations);
249
+ fact("PORTS code", info.portscode);
250
+ fact("Storm surge station", info.stormsurge);
251
+ fact("High-tide-flooding history available", info.HTFhistorical);
252
+ if (typeof info.expand === "string" && info.expand) {
253
+ fact("Expandable sub-resources", info.expand);
254
+ }
255
+ if (params.expand?.length) {
256
+ lines.push("", `Expanded sub-resources (${params.expand.join(", ")}) are included in the JSON payload.`);
257
+ }
258
+ lines.push("", '_Use response_format "json" for the complete record._');
259
+ return respond(params.response_format, structured, lines.join("\n"));
260
+ }
261
+ catch (error) {
262
+ return respondError(error);
263
+ }
264
+ });
265
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Water level observation and tide prediction tools.
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ export declare function registerWaterTools(server: McpServer): void;