@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
package/README.md
CHANGED
|
@@ -1,234 +1,199 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🌊 NOAA Tides & Currents MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@ryancardin/noaa-tides-currents-mcp-server)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://modelcontextprotocol.io/)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
**A Model Context Protocol server for NOAA CO-OPS Tides and Currents data**
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
16
|
+
</div>
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
- npm or yarn
|
|
18
|
+
---
|
|
22
19
|
|
|
23
|
-
##
|
|
20
|
+
## Quick Start
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
```bash
|
|
23
|
+
# Run immediately with npx
|
|
24
|
+
npx @ryancardin/noaa-tides-currents-mcp-server
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
# Or the short alias
|
|
27
|
+
npx noaa-mcp
|
|
28
|
+
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
1. Clone this repository
|
|
35
|
-
2. Install dependencies
|
|
43
|
+
Claude Code one-liner:
|
|
36
44
|
|
|
37
45
|
```bash
|
|
38
|
-
|
|
46
|
+
claude mcp add noaa -- npx -y @ryancardin/noaa-tides-currents-mcp-server
|
|
39
47
|
```
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
### HTTP mode (optional)
|
|
42
50
|
|
|
43
51
|
```bash
|
|
44
|
-
|
|
52
|
+
npx noaa-mcp --http --port 3000 # stateless streamable HTTP at http://localhost:3000/mcp
|
|
45
53
|
```
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
No API key is required — NOAA's CO-OPS APIs are open.
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
npm start
|
|
51
|
-
```
|
|
57
|
+
---
|
|
52
58
|
|
|
53
|
-
##
|
|
59
|
+
## Tools (23)
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
### Observations & Predictions (Data API)
|
|
56
62
|
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|