@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
package/README.md CHANGED
@@ -1,234 +1,199 @@
1
- # LocalTides MCP Server
1
+ # 🌊 NOAA Tides & Currents MCP Server
2
2
 
3
- [![smithery badge](https://smithery.ai/badge/@RyanCardin15/noaa-tidesandcurrents-mcp)](https://smithery.ai/server/@RyanCardin15/noaa-tidesandcurrents-mcp)
3
+ <div align="center">
4
4
 
5
- This is an MCP (Model Context Protocol) server that provides tools for interacting with the NOAA Tides and Currents API using the FastMCP framework.
5
+ [![npm version](https://img.shields.io/npm/v/@ryancardin/noaa-tides-currents-mcp-server?style=for-the-badge&logo=npm&color=blue)](https://www.npmjs.com/package/@ryancardin/noaa-tides-currents-mcp-server)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
8
+ [![MCP](https://img.shields.io/badge/MCP-Model_Context_Protocol-green?style=for-the-badge)](https://modelcontextprotocol.io/)
6
9
 
7
- ## Features
10
+ **A Model Context Protocol server for NOAA CO-OPS Tides and Currents data**
8
11
 
9
- - Water Level data retrieval (real-time and historical)
10
- - Tide Predictions (high/low or interval-based)
11
- - Currents data (real-time and historical)
12
- - Current predictions
13
- - Station metadata retrieval
14
- - Wind, air temperature, water temperature, and other meteorological data
15
- - Moon phase information (past, present, and future)
16
- - Sun rise/set and position data (past, present, and future)
12
+ Water levels · tide predictions · currents · marine weather · station metadata ·
13
+ tidal datums · harmonic constituents · sea level trends & projections ·
14
+ high tide flooding · sun & moon calculations
17
15
 
18
- ## Prerequisites
16
+ </div>
19
17
 
20
- - Node.js (v18 or higher)
21
- - npm or yarn
18
+ ---
22
19
 
23
- ## Setup
20
+ ## Quick Start
24
21
 
25
- ### Installing via Smithery
22
+ ```bash
23
+ # Run immediately with npx
24
+ npx @ryancardin/noaa-tides-currents-mcp-server
26
25
 
27
- To install NOAA Tides and Currents for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@RyanCardin15/tidesandcurrents):
26
+ # Or the short alias
27
+ npx noaa-mcp
28
+ ```
28
29
 
29
- ```bash
30
- npx -y @smithery/cli install @RyanCardin15/tidesandcurrents --client claude
30
+ ### Claude Desktop / Claude Code configuration
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "noaa": {
36
+ "command": "npx",
37
+ "args": ["-y", "@ryancardin/noaa-tides-currents-mcp-server"]
38
+ }
39
+ }
40
+ }
31
41
  ```
32
42
 
33
- ### Manual Installation
34
- 1. Clone this repository
35
- 2. Install dependencies
43
+ Claude Code one-liner:
36
44
 
37
45
  ```bash
38
- npm install
46
+ claude mcp add noaa -- npx -y @ryancardin/noaa-tides-currents-mcp-server
39
47
  ```
40
48
 
41
- 3. Build the TypeScript code
49
+ ### HTTP mode (optional)
42
50
 
43
51
  ```bash
44
- npm run build
52
+ npx noaa-mcp --http --port 3000 # stateless streamable HTTP at http://localhost:3000/mcp
45
53
  ```
46
54
 
47
- 4. Start the server
55
+ No API key is required — NOAA's CO-OPS APIs are open.
48
56
 
49
- ```bash
50
- npm start
51
- ```
57
+ ---
52
58
 
53
- ## Usage
59
+ ## Tools (23)
54
60
 
55
- This MCP server can be used with any MCP host such as Claude Desktop, which allows you to use the NOAA Tides and Currents API through the MCP protocol.
61
+ ### Observations & Predictions (Data API)
56
62
 
57
- You can also test it directly using the `fastmcp` command-line tool:
63
+ | Tool | What it does |
64
+ |---|---|
65
+ | `noaa_get_water_levels` | Observed water levels: 1-minute, 6-minute, or hourly series, preliminary/verified quality flags decoded |
66
+ | `noaa_get_water_level_summaries` | high_low (HH/H/L/LL daily extremes), daily_mean (Great Lakes), daily_max_min, monthly_mean datum tables |
67
+ | `noaa_get_tide_predictions` | Harmonic tide predictions — `hilo` high/low events (up to 10 years) or interval series |
68
+ | `noaa_get_currents` | Observed current speed/direction by depth bin (ADCP), optional beam diagnostics |
69
+ | `noaa_get_current_predictions` | Predicted currents — `max_slack` flood/ebb/slack events or interval series |
70
+ | `noaa_get_meteorological_data` | Wind, air/water temperature, pressure, air gap (bridge clearance), conductivity, visibility, humidity, salinity |
58
71
 
59
- ```bash
60
- npx fastmcp dev dist/index.js
61
- ```
72
+ ### Station Discovery & Metadata (Metadata API)
73
+
74
+ | Tool | What it does |
75
+ |---|---|
76
+ | `noaa_search_stations` | Search the station directory by capability type, name substring, state — paginated |
77
+ | `noaa_find_nearest_stations` | Nearest stations to any lat/lon (great-circle, cached directory), filterable by type |
78
+ | `noaa_get_station_info` | Full station record with expandable sensors, flood levels, benchmarks, bins, deployments... |
79
+ | `noaa_get_station_datums` | Tidal datum elevations (MLLW, MSL, MHHW, NAVD88...), HAT/LAT, historic extremes, current or superseded epoch |
80
+ | `noaa_get_harmonic_constituents` | The M2/S2/K1/... constituents behind a station's predictions (water level or current ellipse form) |
81
+ | `noaa_get_prediction_offsets` | Subordinate-station time/height offsets from their reference stations (tide or current) |
82
+
83
+ ### Climate & Derived Products (DPAPI)
84
+
85
+ | Tool | What it does |
86
+ |---|---|
87
+ | `noaa_get_sea_level_trends` | Long-term relative sea level trend with error bars and observation period |
88
+ | `noaa_get_sea_level_rise_projections` | 2022 Interagency SLR scenario projections per decade through 2150 |
89
+ | `noaa_get_extreme_water_levels` | Annual exceedance probability levels (e.g. the "100-year" water level) |
90
+ | `noaa_get_top_ten_water_levels` | Highest water levels ever recorded, with causal events (hurricanes, nor'easters) |
91
+ | `noaa_get_high_tide_flooding` | HTF flood-day counts (daily/monthly/seasonal/annual), outlooks, decadal projections, likelihoods |
92
+
93
+ ### Astronomy (computed locally)
94
+
95
+ | Tool | What it does |
96
+ |---|---|
97
+ | `astro_get_moon_phase` | Phase, illumination, age, distance for a date or range (spring/neap tide context) |
98
+ | `astro_get_next_moon_phase` | Next new/full/quarter moon date(s) |
99
+ | `astro_get_sun_times` | Sunrise/sunset, twilights, golden hour, day length for any location/date |
100
+ | `astro_get_sun_position` | Azimuth/altitude (+ approximate declination/RA) |
101
+ | `astro_get_next_sun_event` | Next occurrence(s) of any sun event |
102
+
103
+ ### Reference
104
+
105
+ | Tool | What it does |
106
+ |---|---|
107
+ | `noaa_get_reference_guide` | Curated NOAA reference: products, datums, units, time zones, intervals, station types, data limits, quality flags, date formats |
108
+
109
+ Every tool supports `response_format: "markdown"` (readable tables with units spelled out — the default) or `"json"` (complete structured payload), and attaches structured content for MCP clients that consume it.
110
+
111
+ ## Resources
112
+
113
+ - `noaa://guide/getting-started` — workflow recipes and common pitfalls
114
+ - `noaa://reference/{topic}` — the nine reference topics above as pinnable resources
115
+
116
+ ## Prompts
62
117
 
63
- Or, you can use the MCP Inspector:
118
+ - `tide_report` — tide report for a place/station and date
119
+ - `boating_conditions` — pre-departure briefing: tides, currents, wind, daylight
120
+ - `station_flood_risk` — flood risk profile: HTF history, extremes, trends, projections
121
+ - `station_overview` — everything a station offers
122
+
123
+ ---
124
+
125
+ ## The Nuances (handled for you)
126
+
127
+ These are the things that make NOAA's API tricky — this server encodes them:
128
+
129
+ - **Datums matter.** Heights are meaningless without a vertical reference. MLLW (chart datum) is the default; stations differ in which datums they support (Great Lakes use IGLD/LWD and have **no tide predictions**). `noaa_get_station_datums` gives the conversion table.
130
+ - **Units are asymmetric.** `metric` means m/s for wind but **cm/s for currents**; air pressure is millibars and salinity PSU in *both* systems. Every response labels its units.
131
+ - **Per-product request-span limits** (4 days for 1-minute data, 31 days for 6-minute, 1 year hourly, 10 years for hilo predictions...) are validated client-side with actionable messages before hitting NOAA.
132
+ - **Two station ID schemes.** Water-level/met stations are 7-digit numeric (`9414290`); current stations are alphanumeric (`cb0102`).
133
+ - **Reference vs subordinate stations.** Subordinate (S) prediction stations only support `hilo` predictions, derived by offsets from a reference (R) station.
134
+ - **`daily_mean` requires local standard time** and only exists for Great Lakes stations — enforced automatically.
135
+ - **Quality flags decoded.** Preliminary vs verified data, sigma, flag alphabets (which differ between preliminary and verified!), and HH/H/L/LL tide types are explained inline.
136
+ - **Predictions are astronomical** — storm surge is not included; compare with observed water levels.
137
+ - **Station directory is cached** (6 h) so nearest-station searches don't refetch thousands of records.
138
+
139
+ ---
140
+
141
+ ## Usage Examples
142
+
143
+ > "When is high tide in Boston tomorrow?"
144
+
145
+ 1. `noaa_find_nearest_stations` (type `tidepredictions`) → `8443970 BOSTON`
146
+ 2. `noaa_get_tide_predictions` (interval `hilo`) → high/low times & heights above MLLW
147
+
148
+ > "How strong will the current be in the Cape Cod Canal this afternoon?"
149
+
150
+ 1. `noaa_find_nearest_stations` (type `currentpredictions`)
151
+ 2. `noaa_get_current_predictions` (interval `max_slack`) → max flood/ebb (knots) and slack times
152
+
153
+ > "How often does Providence flood now vs 20 years ago, and what's projected for 2050?"
154
+
155
+ 1. `noaa_get_high_tide_flooding` (report `annual`, range 25)
156
+ 2. `noaa_get_high_tide_flooding` (report `projections`, decade 2050)
157
+ 3. `noaa_get_sea_level_trends` + `noaa_get_sea_level_rise_projections`
158
+
159
+ ---
160
+
161
+ ## Development
64
162
 
65
163
  ```bash
66
- npx fastmcp inspect dist/index.js
164
+ npm install
165
+ npm run build # tsc → dist/
166
+ npm test # vitest unit tests (validation, formatting, astronomy)
167
+ npm run test:live # end-to-end smoke test against the live NOAA API
168
+ npm run inspector # MCP Inspector against dist/index.js
169
+ npm run dev # tsx src/index.ts
67
170
  ```
68
171
 
69
- ### Available Tools
70
-
71
- #### Parameter Definitions
72
-
73
- - `get_parameter_definitions` - Get information about valid parameter values for NOAA API requests
74
- - Parameters:
75
- - `parameter` (string, optional) - Parameter type to get information about (time_zones, datums, units, tide_intervals, current_intervals, velocity_types, products, station_types, date_formats, output_formats). If not provided, returns information about all parameter types.
76
-
77
- #### Water Levels
78
-
79
- - `get_water_levels` - Get water level data for a station
80
- - Parameters:
81
- - `station` (string) - Station ID
82
- - `date` (string, optional) - Date to retrieve data for ("today", "latest", "recent", or specific date)
83
- - `begin_date` (string, optional) - Start date (YYYYMMDD or MM/DD/YYYY)
84
- - `end_date` (string, optional) - End date (YYYYMMDD or MM/DD/YYYY)
85
- - `range` (number, optional) - Number of hours to retrieve data for
86
- - `datum` (string, optional) - Datum to use (MLLW, MSL, etc.)
87
- - `units` (string, optional) - Units to use ("english" or "metric")
88
- - `time_zone` (string, optional) - Time zone (gmt, lst, lst_ldt)
89
- - `format` (string, optional) - Output format (json, xml, csv)
90
-
91
- #### Tide Predictions
92
-
93
- - `get_tide_predictions` - Get tide prediction data
94
- - Parameters:
95
- - `station` (string) - Station ID
96
- - `begin_date` (string) - Start date (YYYYMMDD or MM/DD/YYYY)
97
- - `end_date` (string) - End date (YYYYMMDD or MM/DD/YYYY)
98
- - `datum` (string, optional) - Datum to use (MLLW, MSL, etc.)
99
- - `units` (string, optional) - Units to use ("english" or "metric")
100
- - `time_zone` (string, optional) - Time zone (gmt, lst, lst_ldt)
101
- - `interval` (string, optional) - Interval (hilo, hl, h, or a number for minutes)
102
- - `format` (string, optional) - Output format (json, xml, csv)
103
-
104
- #### Currents
105
-
106
- - `get_currents` - Get currents data for a station
107
- - Parameters:
108
- - `station` (string) - Station ID
109
- - `date` (string, optional) - Date to retrieve data for ("today", "latest", "recent", or specific date)
110
- - `begin_date` (string, optional) - Start date (YYYYMMDD or MM/DD/YYYY)
111
- - `end_date` (string, optional) - End date (YYYYMMDD or MM/DD/YYYY)
112
- - `bin` (number, optional) - Bin number
113
- - `units` (string, optional) - Units to use ("english" or "metric")
114
- - `time_zone` (string, optional) - Time zone (gmt, lst, lst_ldt)
115
- - `format` (string, optional) - Output format (json, xml, csv)
116
-
117
- #### Current Predictions
118
-
119
- - `get_current_predictions` - Get current predictions
120
- - Parameters:
121
- - `station` (string) - Station ID
122
- - `date` (string, optional) - Date to retrieve data for ("today", "latest", "recent", or specific date)
123
- - `begin_date` (string, optional) - Start date (YYYYMMDD or MM/DD/YYYY)
124
- - `end_date` (string, optional) - End date (YYYYMMDD or MM/DD/YYYY)
125
- - `bin` (number, optional) - Bin number
126
- - `interval` (string, optional) - Interval (MAX_SLACK or a number for minutes)
127
- - `vel_type` (string, optional) - Velocity type (speed_dir or default)
128
- - `units` (string, optional) - Units to use ("english" or "metric")
129
- - `time_zone` (string, optional) - Time zone (gmt, lst, lst_ldt)
130
- - `format` (string, optional) - Output format (json, xml, csv)
131
-
132
- #### Meteorological Data
133
-
134
- - `get_meteorological_data` - Get meteorological data
135
- - Parameters:
136
- - `station` (string) - Station ID
137
- - `product` (string) - Product (air_temperature, wind, etc.)
138
- - `date` (string, optional) - Date to retrieve data for ("today", "latest", "recent", or specific date)
139
- - `begin_date` (string, optional) - Start date (YYYYMMDD or MM/DD/YYYY)
140
- - `end_date` (string, optional) - End date (YYYYMMDD or MM/DD/YYYY)
141
- - `units` (string, optional) - Units to use ("english" or "metric")
142
- - `time_zone` (string, optional) - Time zone (gmt, lst, lst_ldt)
143
- - `format` (string, optional) - Output format (json, xml, csv)
144
-
145
- #### Station Information
146
-
147
- - `get_stations` - Get list of stations
148
- - Parameters:
149
- - `type` (string, optional) - Station type (waterlevels, currents, etc.)
150
- - `units` (string, optional) - Units to use ("english" or "metric")
151
- - `format` (string, optional) - Output format (json, xml)
152
-
153
- - `get_station_details` - Get detailed information about a station
154
- - Parameters:
155
- - `station` (string) - Station ID
156
- - `units` (string, optional) - Units to use ("english" or "metric")
157
- - `format` (string, optional) - Output format (json, xml)
158
-
159
- #### Moon Phase Information
160
-
161
- - `get_moon_phase` - Get moon phase information for a specific date
162
- - Parameters:
163
- - `date` (string, optional) - Date to get moon phase for (YYYY-MM-DD format). Defaults to current date.
164
- - `latitude` (number, optional) - Latitude for location-specific calculations
165
- - `longitude` (number, optional) - Longitude for location-specific calculations
166
- - `format` (string, optional) - Output format (json or text)
167
-
168
- - `get_moon_phases_range` - Get moon phase information for a date range
169
- - Parameters:
170
- - `start_date` (string) - Start date (YYYY-MM-DD format)
171
- - `end_date` (string) - End date (YYYY-MM-DD format)
172
- - `latitude` (number, optional) - Latitude for location-specific calculations
173
- - `longitude` (number, optional) - Longitude for location-specific calculations
174
- - `format` (string, optional) - Output format (json or text)
175
-
176
- - `get_next_moon_phase` - Get the next occurrence(s) of a specific moon phase
177
- - Parameters:
178
- - `phase` (string) - Moon phase to find (New Moon, First Quarter, Full Moon, Last Quarter)
179
- - `date` (string, optional) - Starting date (YYYY-MM-DD format). Defaults to current date.
180
- - `count` (number, optional) - Number of occurrences to return. Defaults to 1.
181
- - `format` (string, optional) - Output format (json or text)
182
-
183
- #### Sun Rise/Set Information
184
-
185
- - `get_sun_times` - Get sun rise/set and other sun event times for a specific date and location
186
- - Parameters:
187
- - `date` (string, optional) - Date to get sun times for (YYYY-MM-DD format). Defaults to current date.
188
- - `latitude` (number) - Latitude for location-specific calculations
189
- - `longitude` (number) - Longitude for location-specific calculations
190
- - `format` (string, optional) - Output format (json or text)
191
- - `timezone` (string, optional) - Timezone for the results. Defaults to UTC.
192
-
193
- - `get_sun_times_range` - Get sun rise/set and other sun event times for a date range and location
194
- - Parameters:
195
- - `start_date` (string) - Start date (YYYY-MM-DD format)
196
- - `end_date` (string) - End date (YYYY-MM-DD format)
197
- - `latitude` (number) - Latitude for location-specific calculations
198
- - `longitude` (number) - Longitude for location-specific calculations
199
- - `format` (string, optional) - Output format (json or text)
200
- - `timezone` (string, optional) - Timezone for the results. Defaults to UTC.
201
-
202
- - `get_sun_position` - Get sun position information for a specific date, time, and location
203
- - Parameters:
204
- - `date` (string, optional) - Date to get sun position for (YYYY-MM-DD format). Defaults to current date.
205
- - `time` (string, optional) - Time to get sun position for (HH:MM:SS format). Defaults to current time.
206
- - `latitude` (number) - Latitude for location-specific calculations
207
- - `longitude` (number) - Longitude for location-specific calculations
208
- - `format` (string, optional) - Output format (json or text)
209
-
210
- - `get_next_sun_event` - Get the next occurrence(s) of a specific sun event
211
- - Parameters:
212
- - `event` (string) - Sun event to find (sunrise, sunset, dawn, dusk, solarNoon, etc.)
213
- - `date` (string, optional) - Starting date (YYYY-MM-DD format). Defaults to current date.
214
- - `latitude` (number) - Latitude for location-specific calculations
215
- - `longitude` (number) - Longitude for location-specific calculations
216
- - `count` (number, optional) - Number of occurrences to return. Defaults to 1.
217
- - `format` (string, optional) - Output format (json or text)
218
- - `timezone` (string, optional) - Timezone for the results. Defaults to UTC.
219
-
220
- ## API Documentation
221
-
222
- NOAA Tides and Currents API documentation can be found at:
223
- - CO-OPS Data API: https://api.tidesandcurrents.noaa.gov/api/prod/
224
- - CO-OPS Metadata API: https://api.tidesandcurrents.noaa.gov/mdapi/prod/
225
- - CO-OPS Derived Product API: https://api.tidesandcurrents.noaa.gov/dpapi/prod/
172
+ ### Architecture
226
173
 
227
- ## License
174
+ ```
175
+ src/
176
+ ├── index.ts # entry point: stdio (default) or --http streamable HTTP
177
+ ├── constants.ts # API base URLs, timeouts, cache TTLs, response limits
178
+ ├── client/ # shared HTTP layer (retry/backoff, error mapping) + TTL cache
179
+ ├── validation/ # date normalization + per-product span limit enforcement
180
+ ├── format/ # unit labeling, flag legends, markdown/json response shaping
181
+ ├── schemas/ # shared Zod field schemas with nuance-carrying descriptions
182
+ ├── services/ # Data API, Metadata API, DPAPI, moon & sun services
183
+ ├── tools/ # 23 tool registrations grouped by domain
184
+ ├── resources/ # noaa:// reference resources
185
+ ├── prompts/ # workflow prompt templates
186
+ └── reference/ # curated NOAA reference content
187
+ ```
228
188
 
229
- MIT
189
+ Data sources:
190
+ - **Data API** — `api.tidesandcurrents.noaa.gov/api/prod/datagetter`
191
+ - **Metadata API** — `api.tidesandcurrents.noaa.gov/mdapi/prod/webapi`
192
+ - **Derived Product API** — `api.tidesandcurrents.noaa.gov/dpapi/prod/webapi`
193
+ - **Astronomy** — [suncalc](https://github.com/mourner/suncalc), computed locally
230
194
 
195
+ ## License
231
196
 
232
- <a href="https://glama.ai/mcp/servers/ro2rz2c734">
233
- <img width="380" height="200" src="https://glama.ai/mcp/servers/ro2rz2c734/badge" />
197
+ MIT © Ryan Cardin
234
198
 
199
+ NOAA data is provided by the NOAA Center for Operational Oceanographic Products and Services (CO-OPS). This project is not affiliated with or endorsed by NOAA.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Minimal in-memory TTL cache used for NOAA metadata responses.
3
+ * Station directories and per-station metadata change rarely, so caching
4
+ * them avoids re-fetching multi-thousand-entry lists on every tool call.
5
+ */
6
+ export declare class TtlCache {
7
+ private entries;
8
+ get<T>(key: string): T | undefined;
9
+ set<T>(key: string, value: T, ttlMs: number): void;
10
+ /** Fetch-through helper: returns cached value or runs `loader` and caches it. */
11
+ getOrLoad<T>(key: string, ttlMs: number, loader: () => Promise<T>): Promise<T>;
12
+ clear(): void;
13
+ }
14
+ /** Shared cache instance for the server process. */
15
+ export declare const cache: TtlCache;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Minimal in-memory TTL cache used for NOAA metadata responses.
3
+ * Station directories and per-station metadata change rarely, so caching
4
+ * them avoids re-fetching multi-thousand-entry lists on every tool call.
5
+ */
6
+ const MAX_ENTRIES = 200;
7
+ export class TtlCache {
8
+ entries = new Map();
9
+ get(key) {
10
+ const entry = this.entries.get(key);
11
+ if (!entry)
12
+ return undefined;
13
+ if (Date.now() > entry.expiresAt) {
14
+ this.entries.delete(key);
15
+ return undefined;
16
+ }
17
+ return entry.value;
18
+ }
19
+ set(key, value, ttlMs) {
20
+ // Evict the oldest entry when full (insertion order approximates LRU
21
+ // well enough for this workload).
22
+ if (this.entries.size >= MAX_ENTRIES && !this.entries.has(key)) {
23
+ const oldest = this.entries.keys().next().value;
24
+ if (oldest !== undefined)
25
+ this.entries.delete(oldest);
26
+ }
27
+ this.entries.delete(key);
28
+ this.entries.set(key, { value, expiresAt: Date.now() + ttlMs });
29
+ }
30
+ /** Fetch-through helper: returns cached value or runs `loader` and caches it. */
31
+ async getOrLoad(key, ttlMs, loader) {
32
+ const cached = this.get(key);
33
+ if (cached !== undefined)
34
+ return cached;
35
+ const value = await loader();
36
+ this.set(key, value, ttlMs);
37
+ return value;
38
+ }
39
+ clear() {
40
+ this.entries.clear();
41
+ }
42
+ }
43
+ /** Shared cache instance for the server process. */
44
+ export const cache = new TtlCache();
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared HTTP layer for all three NOAA CO-OPS API surfaces.
3
+ *
4
+ * Responsibilities:
5
+ * - one axios instance with a sane timeout and identifying User-Agent
6
+ * - retry with backoff on transient failures (network, 5xx, 429)
7
+ * - mapping upstream errors to actionable, agent-friendly messages
8
+ * (the Data API returns structured `{error:{message}}` bodies; the
9
+ * Metadata API returns bare 404s with no body — they are handled
10
+ * differently on purpose)
11
+ */
12
+ /** Error thrown for any NOAA API failure, with a message safe to show the agent. */
13
+ export declare class NoaaApiError extends Error {
14
+ readonly status?: number | undefined;
15
+ constructor(message: string, status?: number | undefined);
16
+ }
17
+ /** Strip undefined/null/empty-string params so URLs stay clean. */
18
+ export declare function cleanParams(params: Record<string, string | number | boolean | undefined | null>): Record<string, string>;
19
+ /**
20
+ * Data API GET. Always requests JSON and tags the query with our application
21
+ * name. The Data API can return HTTP 200 with an error body, so both paths
22
+ * are checked.
23
+ */
24
+ export declare function fetchDataApi<T = Record<string, unknown>>(params: Record<string, string | number | boolean | undefined | null>): Promise<T>;
25
+ /** Metadata API GET: path like "/stations.json" or "/stations/8454000/datums.json". */
26
+ export declare function fetchMetadataApi<T = Record<string, unknown>>(path: string, params?: Record<string, string | number | boolean | undefined | null>): Promise<T>;
27
+ /** Derived Product API GET: path like "/htf/htf_annual.json". */
28
+ export declare function fetchDpapi<T = Record<string, unknown>>(path: string, params?: Record<string, string | number | boolean | undefined | null>): Promise<T>;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Shared HTTP layer for all three NOAA CO-OPS API surfaces.
3
+ *
4
+ * Responsibilities:
5
+ * - one axios instance with a sane timeout and identifying User-Agent
6
+ * - retry with backoff on transient failures (network, 5xx, 429)
7
+ * - mapping upstream errors to actionable, agent-friendly messages
8
+ * (the Data API returns structured `{error:{message}}` bodies; the
9
+ * Metadata API returns bare 404s with no body — they are handled
10
+ * differently on purpose)
11
+ */
12
+ import axios from "axios";
13
+ import { APPLICATION_NAME, DATA_API_BASE_URL, DPAPI_BASE_URL, MAX_RETRIES, METADATA_API_BASE_URL, REQUEST_TIMEOUT_MS, } from "../constants.js";
14
+ /** Error thrown for any NOAA API failure, with a message safe to show the agent. */
15
+ export class NoaaApiError extends Error {
16
+ status;
17
+ constructor(message, status) {
18
+ super(message);
19
+ this.status = status;
20
+ this.name = "NoaaApiError";
21
+ }
22
+ }
23
+ const http = axios.create({
24
+ timeout: REQUEST_TIMEOUT_MS,
25
+ headers: {
26
+ Accept: "application/json",
27
+ "User-Agent": `${APPLICATION_NAME}/2.0`,
28
+ },
29
+ });
30
+ function sleep(ms) {
31
+ return new Promise((resolve) => setTimeout(resolve, ms));
32
+ }
33
+ function isRetryable(error) {
34
+ if (!axios.isAxiosError(error))
35
+ return false;
36
+ if (!error.response)
37
+ return true; // network error / timeout
38
+ const status = error.response.status;
39
+ return status === 429 || status >= 500;
40
+ }
41
+ /** Strip undefined/null/empty-string params so URLs stay clean. */
42
+ export function cleanParams(params) {
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(params)) {
45
+ if (value === undefined || value === null || value === "")
46
+ continue;
47
+ out[key] = String(value);
48
+ }
49
+ return out;
50
+ }
51
+ async function getWithRetry(url, params) {
52
+ let lastError;
53
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
54
+ try {
55
+ const response = await http.get(url, { params });
56
+ return response.data;
57
+ }
58
+ catch (error) {
59
+ lastError = error;
60
+ if (attempt < MAX_RETRIES && isRetryable(error)) {
61
+ await sleep(500 * Math.pow(3, attempt)); // 500ms, 1.5s
62
+ continue;
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+ throw lastError;
68
+ }
69
+ /** Shape of Data API / DPAPI error bodies: {"error": {"message": "..."}} */
70
+ function extractUpstreamMessage(data) {
71
+ if (data && typeof data === "object") {
72
+ const err = data.error;
73
+ if (err && typeof err.message === "string")
74
+ return err.message.trim();
75
+ }
76
+ if (typeof data === "string" && data.includes("<error>")) {
77
+ const match = data.match(/<error>([\s\S]*?)<\/error>/);
78
+ if (match)
79
+ return match[1].trim();
80
+ }
81
+ return undefined;
82
+ }
83
+ /** Add a helpful next step based on common NOAA error message patterns. */
84
+ function adviceFor(message) {
85
+ const lower = message.toLowerCase();
86
+ // Check "no data" before "station": NOAA's no-data message mentions the
87
+ // word "station" too, and the availability hint is the useful one.
88
+ if (lower.includes("no data")) {
89
+ return ' The station may not collect this product, or the requested window may pre-date its record (recent current-meter deployments come and go). Check availability with noaa_get_station_info, or try date="recent".';
90
+ }
91
+ if (lower.includes("datum")) {
92
+ return " Use noaa_get_station_datums to see which datums this station supports (Great Lakes stations use IGLD/LWD; coastal stations use MLLW/MSL/etc.).";
93
+ }
94
+ if (lower.includes("date") || lower.includes("range")) {
95
+ return ' Accepted date formats: yyyyMMdd, "yyyyMMdd HH:mm", MM/dd/yyyy, or ISO yyyy-MM-dd. Check the per-product maximum span with noaa_get_reference_guide (topic "data_limits").';
96
+ }
97
+ if (lower.includes("station")) {
98
+ return ' Verify the station ID with noaa_search_stations or noaa_find_nearest_stations (water-level stations use 7-digit numeric IDs; current stations use alphanumeric IDs like "cb0102").';
99
+ }
100
+ if (lower.includes("prediction")) {
101
+ return ' Note: Great Lakes stations have no tide predictions, and subordinate (type "S") stations only support interval=hilo.';
102
+ }
103
+ return "";
104
+ }
105
+ function toNoaaError(error, apiLabel) {
106
+ if (axios.isAxiosError(error)) {
107
+ const axErr = error;
108
+ if (axErr.response) {
109
+ const status = axErr.response.status;
110
+ const upstream = extractUpstreamMessage(axErr.response.data);
111
+ if (upstream) {
112
+ return new NoaaApiError(`NOAA ${apiLabel} error: ${upstream}${adviceFor(upstream)}`, status);
113
+ }
114
+ if (status === 404) {
115
+ return new NoaaApiError(`NOAA ${apiLabel} error: not found (404). The station ID may be wrong or this station does not have the requested resource. Verify with noaa_search_stations.`, status);
116
+ }
117
+ if (status === 429) {
118
+ return new NoaaApiError(`NOAA ${apiLabel} error: rate limited (429). NOAA throttles heavy query volume — wait a moment and retry, and request narrower date ranges.`, status);
119
+ }
120
+ return new NoaaApiError(`NOAA ${apiLabel} error: HTTP ${status}.`, status);
121
+ }
122
+ if (axErr.code === "ECONNABORTED") {
123
+ return new NoaaApiError(`NOAA ${apiLabel} error: request timed out after ${REQUEST_TIMEOUT_MS / 1000}s. Try a narrower date range or retry.`);
124
+ }
125
+ return new NoaaApiError(`NOAA ${apiLabel} error: network failure (${axErr.code ?? "unknown"}).`);
126
+ }
127
+ return new NoaaApiError(`NOAA ${apiLabel} error: ${error instanceof Error ? error.message : String(error)}`);
128
+ }
129
+ /**
130
+ * Data API GET. Always requests JSON and tags the query with our application
131
+ * name. The Data API can return HTTP 200 with an error body, so both paths
132
+ * are checked.
133
+ */
134
+ export async function fetchDataApi(params) {
135
+ const query = cleanParams({
136
+ ...params,
137
+ application: APPLICATION_NAME,
138
+ format: "json",
139
+ });
140
+ try {
141
+ const data = await getWithRetry(DATA_API_BASE_URL, query);
142
+ const upstream = extractUpstreamMessage(data);
143
+ if (upstream) {
144
+ throw new NoaaApiError(`NOAA Data API error: ${upstream}${adviceFor(upstream)}`);
145
+ }
146
+ return data;
147
+ }
148
+ catch (error) {
149
+ if (error instanceof NoaaApiError)
150
+ throw error;
151
+ throw toNoaaError(error, "Data API");
152
+ }
153
+ }
154
+ /** Metadata API GET: path like "/stations.json" or "/stations/8454000/datums.json". */
155
+ export async function fetchMetadataApi(path, params = {}) {
156
+ try {
157
+ return await getWithRetry(`${METADATA_API_BASE_URL}${path}`, cleanParams(params));
158
+ }
159
+ catch (error) {
160
+ throw toNoaaError(error, "Metadata API");
161
+ }
162
+ }
163
+ /** Derived Product API GET: path like "/htf/htf_annual.json". */
164
+ export async function fetchDpapi(path, params = {}) {
165
+ try {
166
+ const data = await getWithRetry(`${DPAPI_BASE_URL}${path}`, cleanParams(params));
167
+ const upstream = extractUpstreamMessage(data);
168
+ if (upstream) {
169
+ throw new NoaaApiError(`NOAA Derived Product API error: ${upstream}${adviceFor(upstream)}`);
170
+ }
171
+ return data;
172
+ }
173
+ catch (error) {
174
+ if (error instanceof NoaaApiError)
175
+ throw error;
176
+ throw toNoaaError(error, "Derived Product API");
177
+ }
178
+ }