@cyanheads/noaa-marine-mcp-server 0.5.1 → 0.6.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.
Files changed (59) hide show
  1. package/AGENTS.md +16 -6
  2. package/CLAUDE.md +16 -6
  3. package/README.md +18 -5
  4. package/changelog/0.5.x/0.5.2.md +19 -0
  5. package/changelog/0.6.x/0.6.0.md +19 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.d.ts.map +1 -1
  9. package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js +8 -3
  10. package/dist/mcp-server/resources/definitions/noaa-marine-station.resource.js.map +1 -1
  11. package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts +1 -0
  12. package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts.map +1 -1
  13. package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js +37 -120
  14. package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js.map +1 -1
  15. package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js +1 -2
  17. package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.js +1 -2
  20. package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js +3 -6
  23. package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.d.ts +102 -0
  25. package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.js +321 -0
  27. package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.js +1 -2
  30. package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.js.map +1 -1
  31. package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts.map +1 -1
  32. package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js +2 -4
  33. package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js.map +1 -1
  34. package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts +4 -4
  35. package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts.map +1 -1
  36. package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js +19 -12
  37. package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js.map +1 -1
  38. package/dist/services/coops/coops-service.d.ts +27 -1
  39. package/dist/services/coops/coops-service.d.ts.map +1 -1
  40. package/dist/services/coops/coops-service.js +54 -0
  41. package/dist/services/coops/coops-service.js.map +1 -1
  42. package/dist/services/coops/date-range.d.ts +14 -2
  43. package/dist/services/coops/date-range.d.ts.map +1 -1
  44. package/dist/services/coops/date-range.js +17 -2
  45. package/dist/services/coops/date-range.js.map +1 -1
  46. package/dist/services/coops/row-page.d.ts +1 -1
  47. package/dist/services/coops/row-page.js +1 -1
  48. package/dist/services/coops/station-state.d.ts +44 -0
  49. package/dist/services/coops/station-state.d.ts.map +1 -0
  50. package/dist/services/coops/station-state.js +131 -0
  51. package/dist/services/coops/station-state.js.map +1 -0
  52. package/dist/services/coops/types.d.ts +35 -1
  53. package/dist/services/coops/types.d.ts.map +1 -1
  54. package/dist/services/geo.d.ts +9 -0
  55. package/dist/services/geo.d.ts.map +1 -0
  56. package/dist/services/geo.js +15 -0
  57. package/dist/services/geo.js.map +1 -0
  58. package/package.json +1 -1
  59. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** noaa-marine-mcp-server
4
- **Version:** 0.5.1
4
+ **Version:** 0.6.0
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
@@ -111,8 +111,15 @@ export const noaaMarineStationResource = resource('noaa-marine://station/{statio
111
111
  }),
112
112
  async handler(params, ctx) {
113
113
  const coopsSvc = getCoopsService();
114
- const ndbcSvc = getNdbcService();
115
- const station = await coopsSvc.getStation(params.station_id) ?? ndbcSvc.getStation(params.station_id);
114
+ const id = params.station_id.toUpperCase();
115
+ // Station lookups read the cached catalogs — there is no per-station fetch.
116
+ const [tide, current, waterLevel, ndbc] = await Promise.all([
117
+ coopsSvc.getStations('tidepredictions', ctx),
118
+ coopsSvc.getStations('currentpredictions', ctx),
119
+ coopsSvc.getStations('waterlevels', ctx),
120
+ getNdbcService().getActiveStations(ctx),
121
+ ]);
122
+ const station = [...tide, ...current, ...waterLevel, ...ndbc].find((s) => s.id.toUpperCase() === id);
116
123
  if (!station) throw notFound(`Station ${params.station_id} not found`);
117
124
  return station;
118
125
  },
@@ -227,11 +234,13 @@ src/
227
234
  server-config.ts # NOAA_APPLICATION_ID env var (optional, defaults to server name)
228
235
  services/
229
236
  coops/
230
- coops-service.ts # CO-OPS Tides & Currents API (station cache, data fetch, message classifier, 403 throttle mapping)
231
- date-range.ts # YYYYMMDD / YYYY-MM-DD calendar validation, compaction, and span, shared by the date-ranged tools
237
+ coops-service.ts # CO-OPS Tides & Currents API (station cache, resolved-state cache, data fetch, message classifier, 403 throttle mapping)
238
+ date-range.ts # YYYYMMDD / YYYY-MM-DD calendar validation, compaction, and span, shared by the date-ranged tools; the verification-lag window rule
232
239
  prediction-class.ts # Decodes the catalog `type` letter into a prediction class
233
240
  row-page.ts # Byte-bounded row paging shared by the time-series tools
241
+ station-state.ts # State codes; resolves each station's state (published, else nearest state-bearing row within 25 km)
234
242
  types.ts # CO-OPS domain types
243
+ geo.ts # Haversine distance, shared by proximity search and state resolution
235
244
  ndbc/
236
245
  ndbc-service.ts # NDBC buoy service (active stations XML, realtime text)
237
246
  types.ts # NDBC domain types
@@ -240,6 +249,7 @@ src/
240
249
  noaa-marine-find-stations.tool.ts # noaa_marine_find_stations
241
250
  noaa-marine-get-tide-predictions.tool.ts # noaa_marine_get_tide_predictions
242
251
  noaa-marine-get-water-level.tool.ts # noaa_marine_get_water_level
252
+ noaa-marine-get-monthly-means.tool.ts # noaa_marine_get_monthly_means
243
253
  noaa-marine-get-currents.tool.ts # noaa_marine_get_currents
244
254
  noaa-marine-get-conditions.tool.ts # noaa_marine_get_conditions
245
255
  noaa-marine-get-current-profile.tool.ts # noaa_marine_get_current_profile
@@ -285,7 +295,7 @@ Available skills:
285
295
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
286
296
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
287
297
  | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
288
- | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
298
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
289
299
  | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
290
300
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
291
301
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** noaa-marine-mcp-server
4
- **Version:** 0.5.1
4
+ **Version:** 0.6.0
5
5
  **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
@@ -111,8 +111,15 @@ export const noaaMarineStationResource = resource('noaa-marine://station/{statio
111
111
  }),
112
112
  async handler(params, ctx) {
113
113
  const coopsSvc = getCoopsService();
114
- const ndbcSvc = getNdbcService();
115
- const station = await coopsSvc.getStation(params.station_id) ?? ndbcSvc.getStation(params.station_id);
114
+ const id = params.station_id.toUpperCase();
115
+ // Station lookups read the cached catalogs — there is no per-station fetch.
116
+ const [tide, current, waterLevel, ndbc] = await Promise.all([
117
+ coopsSvc.getStations('tidepredictions', ctx),
118
+ coopsSvc.getStations('currentpredictions', ctx),
119
+ coopsSvc.getStations('waterlevels', ctx),
120
+ getNdbcService().getActiveStations(ctx),
121
+ ]);
122
+ const station = [...tide, ...current, ...waterLevel, ...ndbc].find((s) => s.id.toUpperCase() === id);
116
123
  if (!station) throw notFound(`Station ${params.station_id} not found`);
117
124
  return station;
118
125
  },
@@ -227,11 +234,13 @@ src/
227
234
  server-config.ts # NOAA_APPLICATION_ID env var (optional, defaults to server name)
228
235
  services/
229
236
  coops/
230
- coops-service.ts # CO-OPS Tides & Currents API (station cache, data fetch, message classifier, 403 throttle mapping)
231
- date-range.ts # YYYYMMDD / YYYY-MM-DD calendar validation, compaction, and span, shared by the date-ranged tools
237
+ coops-service.ts # CO-OPS Tides & Currents API (station cache, resolved-state cache, data fetch, message classifier, 403 throttle mapping)
238
+ date-range.ts # YYYYMMDD / YYYY-MM-DD calendar validation, compaction, and span, shared by the date-ranged tools; the verification-lag window rule
232
239
  prediction-class.ts # Decodes the catalog `type` letter into a prediction class
233
240
  row-page.ts # Byte-bounded row paging shared by the time-series tools
241
+ station-state.ts # State codes; resolves each station's state (published, else nearest state-bearing row within 25 km)
234
242
  types.ts # CO-OPS domain types
243
+ geo.ts # Haversine distance, shared by proximity search and state resolution
235
244
  ndbc/
236
245
  ndbc-service.ts # NDBC buoy service (active stations XML, realtime text)
237
246
  types.ts # NDBC domain types
@@ -240,6 +249,7 @@ src/
240
249
  noaa-marine-find-stations.tool.ts # noaa_marine_find_stations
241
250
  noaa-marine-get-tide-predictions.tool.ts # noaa_marine_get_tide_predictions
242
251
  noaa-marine-get-water-level.tool.ts # noaa_marine_get_water_level
252
+ noaa-marine-get-monthly-means.tool.ts # noaa_marine_get_monthly_means
243
253
  noaa-marine-get-currents.tool.ts # noaa_marine_get_currents
244
254
  noaa-marine-get-conditions.tool.ts # noaa_marine_get_conditions
245
255
  noaa-marine-get-current-profile.tool.ts # noaa_marine_get_current_profile
@@ -285,7 +295,7 @@ Available skills:
285
295
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
286
296
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
287
297
  | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
288
- | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
298
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
289
299
  | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
290
300
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
291
301
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  <div align="center">
2
2
  <h1>@cyanheads/noaa-marine-mcp-server</h1>
3
3
  <p><b>Find NOAA tide stations and NDBC buoys, fetch tide predictions, water levels, tidal currents, and live buoy conditions via MCP. STDIO or Streamable HTTP.</b>
4
- <div>7 Tools • 1 Resource</div>
4
+ <div>8 Tools • 1 Resource</div>
5
5
  </p>
6
6
  </div>
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.5.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/noaa-marine-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/noaa-marine-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/noaa-marine-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.6.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/noaa-marine-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/noaa-marine-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/noaa-marine-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -38,6 +38,7 @@ US tide, current, and buoy data from NOAA CO-OPS and NDBC. Find tide, water-leve
38
38
  | `noaa_marine_find_stations` | Find CO-OPS tide/water-level/current stations and NDBC buoys by location, name, state, or data capability. |
39
39
  | `noaa_marine_get_tide_predictions` | High/low tide predictions or a 6-minute curve for a CO-OPS tide station. |
40
40
  | `noaa_marine_get_water_level` | Observed water level at a 6-minute, hourly, high/low, or daily-mean cadence, paired with predictions and a storm-surge residual summary. |
41
+ | `noaa_marine_get_monthly_means` | Verified monthly tidal datums and extremes for a CO-OPS water-level station — the record for sea-level and tidal-range trends. |
41
42
  | `noaa_marine_get_currents` | CO-OPS tidal current predictions — max flood/ebb/slack events or a 6-minute curve. |
42
43
  | `noaa_marine_get_conditions` | Live NDBC buoy conditions: waves, wind, sea-surface and air temperature, pressure. |
43
44
  | `noaa_marine_get_current_profile` | Observed ocean-current depth profile from an NDBC ADCP buoy. |
@@ -56,6 +57,7 @@ All resource data is also reachable via tools — use `noaa_marine_find_stations
56
57
  ### `noaa_marine_find_stations` <sub>tool</sub>
57
58
 
58
59
  - Filter by proximity (`latitude`/`longitude` + `radius_km`, default 100 km, max 1000 km), name/ID substring (matched against both sources; an exact ID match sorts first), US state/territory (CO-OPS only), source (`coops`/`ndbc`/`all`), or `types`: data capabilities (`tide`, `current`, `water_level`, `met`, `current_profile`, `water_quality`) or NDBC platform class (`buoy`)
60
+ - A CO-OPS station whose catalog rows carry no state code — every current station, and some tide and water-level stations — reports and filters on the state of the nearest state-bearing tide or water-level station within 25 km, marked `state_derived: true`; that state can be wrong on waters shared across a state or national border. A station with no such neighbor shows its own non-code catalog value (e.g. `FM`) if it publishes one, which no state filter matches
59
61
  - Returns up to `limit` (default 20, max 200) unified stations with source, coordinates, distance, data capabilities, and — for NDBC — physical platform class (buoy, fixed, oilrig, dart, tao, usv, other)
60
62
  - CO-OPS prediction stations also carry `prediction_class`, a third axis beside capability and platform: a tide station is `reference` (serving `hilo` and the 6-minute curve) or `subordinate` (`hilo` only, with `reference_id` naming where its offsets come from), while a current station reports its class per depth bin in `bins[]` alongside each bin's number and catalog depth in feet — the bin numbers `noaa_marine_get_currents` takes as `bin`
61
63
  - `total_found` and `truncated` report the full match count before the limit is applied
@@ -90,7 +92,18 @@ All resource data is also reachable via tools — use `noaa_marine_find_stations
90
92
  - `residual_summary` (max surge, max drawdown) only when both series are present, computed from the finite observed/predicted pairs across the whole matched series rather than the returned page. Each side clamps at zero, so a window that stayed above prediction reports `max_drawdown: 0` and one that stayed below reports `max_surge: 0`. Reported on `6min` and `hourly` only — observed high and low waters do not occur at the predicted extreme times, so a `high_low` join would rest on a small fraction of the events, and `daily_mean` has no paired series at all; the notice says which applies
91
93
  - A range whose rows fit the response budget returns whole; a longer one returns the leading rows as a page, with `rows_matched`, `rows_returned`, `page_offset`, and `next_offset` on both consumption surfaces. Observations carry the `offset` and the paired predictions follow by time window, so a page's two series always describe one span even after gap rows shorten the observed one
92
94
  - `daily_mean` is requested in local standard time whatever `time_zone` was passed — CO-OPS serves that product in LST only and silently shifts any other zone by a day
93
- - Typed `date_range_exceeded`, `invalid_date_range`, `station_not_found`, `no_data`, `datum_unavailable`, `great_lakes_only` (`daily_mean` at a coastal station), `verified_data_lag`, and `upstream_throttled` errors — `verified_data_lag` for a window CO-OPS has not verified yet, since it verifies the coarser products monthly for the prior month, and `upstream_throttled` for the HTTP 403 CO-OPS answers a burst of requests with (a 403 on the paired prediction fetch alone leaves `predictions_status: "unavailable"` instead, with a notice naming the wait)
95
+ - Typed `date_range_exceeded`, `invalid_date_range`, `station_not_found`, `no_data`, `datum_unavailable`, `great_lakes_only` (`daily_mean` at a coastal station), `verified_data_lag`, and `upstream_throttled` errors — `verified_data_lag` for an `hourly`, `high_low`, or `daily_mean` window ending on or after the first day of the prior month, which CO-OPS may not have verified yet since it verifies the coarser products monthly for the prior month (an earlier window it answers the same way is `no_data`, since waiting cannot help it), and `upstream_throttled` for the HTTP 403 CO-OPS answers a burst of requests with (a 403 on the paired prediction fetch alone leaves `predictions_status: "unavailable"` instead, with a notice naming the wait)
96
+
97
+ ---
98
+
99
+ ### `noaa_marine_get_monthly_means` <sub>tool</sub>
100
+
101
+ - One row per station-month of the CO-OPS `monthly_mean` product: the month's `highest` and `lowest` water, its tidal datums (`mhhw`, `mhw`, `msl`, `mtl`, `mlw`, `mllw`, `dtl`), ranges (`gt`, `mn`, `dhq`, `dlq`), lunitidal intervals in hours (`hwi`, `lwi`), and CO-OPS's `inferred` code passed through verbatim — a code (`0`, `1`, `11` observed), not a boolean
102
+ - Heights are relative to the requested datum — eleven reference planes: MLLW (default), MHHW, MHW, MTL, MSL, MLW, NAVD, STND, IGLD and LWD (Great Lakes only), CRD (Columbia River only) — in `english` feet or `metric` meters. The ranges (`gt`, `mn`, `dhq`, `dlq`) are differences between two planes, so they read the same under every datum. A value CO-OPS publishes as an empty string is omitted, never zeroed — a Great Lakes station at `IGLD` carries only `highest`, `msl`, and `lowest`
103
+ - `begin_date`/`end_date` as `YYYYMMDD` or `YYYY-MM-DD`, spanning up to 73,000 days (the ceiling CO-OPS names), rejected locally before the call. The months the two dates fall in are the first and last returned; CO-OPS answers one month past `end_date`, and that month is dropped
104
+ - A range whose months fit the response budget returns whole; a longer one — decades of months — returns the leading months as a page with `rows_matched`, `rows_returned`, `page_offset`, and `next_offset`. Walk it with `offset`; an `offset` past the last month is an empty page rather than an error
105
+ - Typed `invalid_date_range`, `date_range_exceeded`, `station_not_found`, `datum_unavailable`, `verified_data_lag`, `no_data`, and `upstream_throttled` errors. CO-OPS answers an unpublished window with one sentence whatever the cause, so a window ending on or after the first day of the prior month is `verified_data_lag` and any earlier one is `no_data`
106
+ - Returns the monthly record, not a trend — fitting one is left to the caller
94
107
 
95
108
  ---
96
109
 
@@ -137,7 +150,7 @@ All resource data is also reachable via tools — use `noaa_marine_find_stations
137
150
 
138
151
  ### `noaa-marine://station/{station_id}` <sub>resource</sub>
139
152
 
140
- - Station record as `application/json` — name, coordinates, source, capabilities, state, the CO-OPS `prediction_class` (with `reference_id` or per-bin `bins[]`, exactly as on `noaa_marine_find_stations`), and (NDBC) platform class
153
+ - Station record as `application/json` — name, coordinates, source, capabilities, state (with `state_derived`, resolved as on `noaa_marine_find_stations`), the CO-OPS `prediction_class` (with `reference_id` or per-bin `bins[]`, exactly as on `noaa_marine_find_stations`), and (NDBC) platform class
141
154
  - `station_id` comes from `noaa_marine_find_stations`
142
155
  - Typed `station_not_found` when both catalogs were read and neither carries the ID, and `source_unavailable` when a catalog could not be read — a station only the unread catalog carries is never reported as nonexistent. When the unread CO-OPS catalog was throttled (HTTP 403), the recovery names the couple-of-minutes wait
143
156
  - Cached with a 6-hour TTL (`cacheHint`)
@@ -329,7 +342,7 @@ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `
329
342
  | `src/config/` | `NOAA_APPLICATION_ID` env var parsing with Zod. |
330
343
  | `src/services/coops/` | CO-OPS Tides & Currents API client: station list cache, data fetch, error detection. |
331
344
  | `src/services/ndbc/` | NDBC buoy service: active stations XML parser, realtime text parser. |
332
- | `src/mcp-server/tools/` | Seven tool definitions (`*.tool.ts`). |
345
+ | `src/mcp-server/tools/` | Eight tool definitions (`*.tool.ts`). |
333
346
  | `src/mcp-server/resources/` | Station metadata resource (`noaa-marine-station.resource.ts`). |
334
347
  | `tests/` | Vitest tests mirroring `src/`. |
335
348
  | `docs/` | Design doc and directory tree. |
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "Derived state for stateless CO-OPS stations, truncation notice fix"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.5.2 — 2026-09-22
8
+
9
+ ## Added
10
+
11
+ - **`state_derived`** on `noaa_marine_find_stations` and the station resource: a CO-OPS station whose own catalog rows carry no state code — every current station, plus some tide and water-level stations — now resolves to the state of the nearest state-bearing tide or water-level row within 25 km, and the `state` filter matches that resolved value ([#36](https://github.com/cyanheads/noaa-marine-mcp-server/issues/36))
12
+
13
+ ## Changed
14
+
15
+ - Haversine distance moved to `src/services/geo.ts` and the state-code list to `src/services/coops/station-state.ts`, shared by the proximity search and state resolution; `CLAUDE.md`/`AGENTS.md` resource example and structure tree synced with the code
16
+
17
+ ## Fixed
18
+
19
+ - **`noaa_marine_find_stations` truncation notice** — a capped search disclosed `shown`/`cap`/`truncationCeiling` through `ctx.enrich.truncated()`, none of which the `enrichment` block declared, so the framework stripped them before they reached the caller; the guidance now composes into the already-declared `notice`, and the `content[]` trailer no longer prints a stray `**truncated:**` line ([#38](https://github.com/cyanheads/noaa-marine-mcp-server/issues/38))
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "New noaa_marine_get_monthly_means tool, window-based verified_data_lag fix"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.6.0 — 2026-09-22
8
+
9
+ ## Added
10
+
11
+ - **`noaa_marine_get_monthly_means`** — verified monthly tidal datums and extremes for a CO-OPS water-level station, one row per station-month: highest/lowest water, tidal datums (MHHW, MHW, MSL, MTL, MLW, MLLW, DTL), ranges (GT, MN, DHQ, DLQ), lunitidal intervals (HWI, LWI), and CO-OPS's `inferred` code verbatim. Wraps the `monthly_mean` product, sent as `time_zone=lst` and trimmed of the resulting overrun month; empty-string upstream values are omitted rather than parsed to 0. Ranges up to 73,000 days, paged by `offset` ([#33](https://github.com/cyanheads/noaa-marine-mcp-server/issues/33))
12
+
13
+ ## Changed
14
+
15
+ - **`noaa_marine_find_stations`** — the `types` value `water_level` now names `noaa_marine_get_monthly_means` alongside `noaa_marine_get_water_level` as the tool that reads it ([#33](https://github.com/cyanheads/noaa-marine-mcp-server/issues/33))
16
+
17
+ ## Fixed
18
+
19
+ - **`noaa_marine_get_water_level`** — an `hourly`, `high_low`, or `daily_mean` window CO-OPS answers with "may not be offered at this station at the requested time" now resolves by window instead of always reading as `verified_data_lag`: a window ending on or after the first day of the prior month is `verified_data_lag`, an earlier one is `no_data`, since waiting cannot help a window before the station's record ([#41](https://github.com/cyanheads/noaa-marine-mcp-server/issues/41))
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { noaaMarineFindStations } from './mcp-server/tools/definitions/noaa-mari
10
10
  import { noaaMarineGetConditions } from './mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js';
11
11
  import { noaaMarineGetCurrentProfile } from './mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.js';
12
12
  import { noaaMarineGetCurrents } from './mcp-server/tools/definitions/noaa-marine-get-currents.tool.js';
13
+ import { noaaMarineGetMonthlyMeans } from './mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.js';
13
14
  import { noaaMarineGetOceanObservations } from './mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.js';
14
15
  import { noaaMarineGetTidePredictions } from './mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js';
15
16
  import { noaaMarineGetWaterLevel } from './mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js';
@@ -23,6 +24,7 @@ await createApp({
23
24
  noaaMarineFindStations,
24
25
  noaaMarineGetTidePredictions,
25
26
  noaaMarineGetWaterLevel,
27
+ noaaMarineGetMonthlyMeans,
26
28
  noaaMarineGetCurrents,
27
29
  noaaMarineGetConditions,
28
30
  noaaMarineGetCurrentProfile,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mEAAmE,CAAC;AAC5G,OAAO,EAAE,2BAA2B,EAAE,MAAM,wEAAwE,CAAC;AACrH,OAAO,EAAE,qBAAqB,EAAE,MAAM,iEAAiE,CAAC;AACxG,OAAO,EAAE,8BAA8B,EAAE,MAAM,2EAA2E,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE;QACL,sBAAsB;QACtB,4BAA4B;QAC5B,uBAAuB;QACvB,qBAAqB;QACrB,uBAAuB;QACvB,2BAA2B;QAC3B,8BAA8B;KAC/B;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QACzD,gBAAgB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QAC7D,0BAA0B,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;KACxE;IACD,YAAY,EAAE,itBAAitB;IAE/tB,KAAK,CAAC,IAAI;QACR,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1D,eAAe,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mEAAmE,CAAC;AAC5G,OAAO,EAAE,2BAA2B,EAAE,MAAM,wEAAwE,CAAC;AACrH,OAAO,EAAE,qBAAqB,EAAE,MAAM,iEAAiE,CAAC;AACxG,OAAO,EAAE,yBAAyB,EAAE,MAAM,sEAAsE,CAAC;AACjH,OAAO,EAAE,8BAA8B,EAAE,MAAM,2EAA2E,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE;QACL,sBAAsB;QACtB,4BAA4B;QAC5B,uBAAuB;QACvB,yBAAyB;QACzB,qBAAqB;QACrB,uBAAuB;QACvB,2BAA2B;QAC3B,8BAA8B;KAC/B;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QACzD,gBAAgB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QAC7D,0BAA0B,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;KACxE;IACD,YAAY,EAAE,itBAAitB;IAE/tB,KAAK,CAAC,IAAI;QACR,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1D,eAAe,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"noaa-marine-station.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;GAqKpC,CAAC"}
1
+ {"version":3,"file":"noaa-marine-station.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;GA0KpC,CAAC"}
@@ -9,7 +9,7 @@ import { currentPredictionClass, tidePredictionClass } from '../../../services/c
9
9
  import { getNdbcService } from '../../../services/ndbc/ndbc-service.js';
10
10
  export const noaaMarineStationResource = resource('noaa-marine://station/{station_id}', {
11
11
  name: 'noaa_marine_station',
12
- description: `Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, data capabilities, the CO-OPS prediction class, and — for NDBC — the physical platform class. The type field is the primary data capability, meaning the same thing here as in noaa_marine_find_stations, and is omitted when the station reports no data capability; platform is the NDBC platform class (buoy, fixed, oilrig, dart, tao, usv, other), a separate axis that CO-OPS stations do not carry. prediction_class is a third axis, again identical to the field of that name on noaa_marine_find_stations: a tide station is reference (serving both hilo and the 6-minute curve) or subordinate (hilo only, with reference_id naming the station its offsets come from), while a current station carries its class per depth bin in bins[], whose bin numbers are what noaa_marine_get_currents takes as bin. CO-OPS station IDs are numeric for tide and water-level stations and alphanumeric for current stations, while NDBC station IDs are 5-character alphanumeric codes — use noaa_marine_find_stations to discover them. A station ID that neither catalog carries is a station_not_found error, distinct from source_unavailable, which says a catalog could not be read and so was never searched.`,
12
+ description: `Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, data capabilities, the CO-OPS prediction class, and — for NDBC — the physical platform class. The type field is the primary data capability, meaning the same thing here as in noaa_marine_find_stations, and is omitted when the station reports no data capability; platform is the NDBC platform class (buoy, fixed, oilrig, dart, tao, usv, other), a separate axis that CO-OPS stations do not carry. prediction_class is a third axis, again identical to the field of that name on noaa_marine_find_stations: a tide station is reference (serving both hilo and the 6-minute curve) or subordinate (hilo only, with reference_id naming the station its offsets come from), while a current station carries its class per depth bin in bins[], whose bin numbers are what noaa_marine_get_currents takes as bin. state is a CO-OPS station's US state or territory code, resolved as on noaa_marine_find_stations: its own catalog code, or — when its catalog rows carry none, as with every current station — the state of the nearest state-bearing tide or water-level station within 25 km, marked state_derived: true and possibly wrong on waters shared across a state or national border; failing both, the station's own non-code catalog value (e.g. FM) as published, and otherwise omitted. CO-OPS station IDs are numeric for tide and water-level stations and alphanumeric for current stations, while NDBC station IDs are 5-character alphanumeric codes — use noaa_marine_find_stations to discover them. A station ID that neither catalog carries is a station_not_found error, distinct from source_unavailable, which says a catalog could not be read and so was never searched.`,
13
13
  mimeType: 'application/json',
14
14
  cacheHint: { ttlMs: 21_600_000, cacheScope: 'public' },
15
15
  params: z.object({
@@ -78,8 +78,13 @@ export const noaaMarineStationResource = resource('noaa-marine://station/{statio
78
78
  // whose bins can mix classes. CO-OPS has no platform class.
79
79
  if (caps[0])
80
80
  result.type = caps[0];
81
- if (match.state)
82
- result.state = match.state;
81
+ // The same resolved state noaa_marine_find_stations reports and filters on.
82
+ const resolved = coopsSvc.stationStates({ tide, current, waterLevel }).get(match.id);
83
+ if (resolved) {
84
+ result.state = resolved.state;
85
+ if (resolved.derived)
86
+ result.state_derived = true;
87
+ }
83
88
  const predictionClass = tidePredictionClass(tideRow?.type);
84
89
  if (predictionClass)
85
90
  result.prediction_class = predictionClass;
@@ -1 +1 @@
1
- {"version":3,"file":"noaa-marine-station.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"noaa-marine-station.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/noaa-marine-station.resource.ts"],"names":[],"mappings":"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"}
@@ -96,6 +96,7 @@ export declare const noaaMarineFindStations: import("@cyanheads/mcp-ts-core").To
96
96
  longitude: z.ZodNumber;
97
97
  distance_km: z.ZodOptional<z.ZodNumber>;
98
98
  state: z.ZodOptional<z.ZodString>;
99
+ state_derived: z.ZodOptional<z.ZodBoolean>;
99
100
  capabilities: z.ZodArray<z.ZodString>;
100
101
  prediction_class: z.ZodOptional<z.ZodString>;
101
102
  reference_id: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"noaa-marine-find-stations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/noaa-marine-find-stations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAmPjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4kBjC,CAAC"}
1
+ {"version":3,"file":"noaa-marine-find-stations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/noaa-marine-find-stations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAiLjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAikBjC,CAAC"}
@@ -6,74 +6,9 @@ import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
7
  import { getCoopsService, isCoopsThrottled } from '../../../services/coops/coops-service.js';
8
8
  import { currentPredictionClass, tidePredictionClass } from '../../../services/coops/prediction-class.js';
9
+ import { STATE_CODES } from '../../../services/coops/station-state.js';
10
+ import { haversineKm } from '../../../services/geo.js';
9
11
  import { getNdbcService } from '../../../services/ndbc/ndbc-service.js';
10
- /** Haversine distance in km between two lat/lon pairs. */
11
- function haversineKm(lat1, lon1, lat2, lon2) {
12
- const R = 6371;
13
- const dLat = ((lat2 - lat1) * Math.PI) / 180;
14
- const dLon = ((lon2 - lon1) * Math.PI) / 180;
15
- const a = Math.sin(dLat / 2) ** 2 +
16
- Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLon / 2) ** 2;
17
- return R * 2 * Math.asin(Math.sqrt(a));
18
- }
19
- const STATE_CODES = [
20
- 'AL',
21
- 'AK',
22
- 'AZ',
23
- 'AR',
24
- 'CA',
25
- 'CO',
26
- 'CT',
27
- 'DE',
28
- 'FL',
29
- 'GA',
30
- 'HI',
31
- 'ID',
32
- 'IL',
33
- 'IN',
34
- 'IA',
35
- 'KS',
36
- 'KY',
37
- 'LA',
38
- 'ME',
39
- 'MD',
40
- 'MA',
41
- 'MI',
42
- 'MN',
43
- 'MS',
44
- 'MO',
45
- 'MT',
46
- 'NE',
47
- 'NV',
48
- 'NH',
49
- 'NJ',
50
- 'NM',
51
- 'NY',
52
- 'NC',
53
- 'ND',
54
- 'OH',
55
- 'OK',
56
- 'OR',
57
- 'PA',
58
- 'RI',
59
- 'SC',
60
- 'SD',
61
- 'TN',
62
- 'TX',
63
- 'UT',
64
- 'VT',
65
- 'VA',
66
- 'WA',
67
- 'WV',
68
- 'WI',
69
- 'WY',
70
- 'DC',
71
- 'PR',
72
- 'VI',
73
- 'GU',
74
- 'AS',
75
- 'MP',
76
- ];
77
12
  /** Filter values only a CO-OPS row can carry. */
78
13
  const COOPS_FILTER_VALUES = ['tide', 'current', 'water_level'];
79
14
  /** Filter values only an NDBC row can carry — three data capabilities plus the buoy platform token. */
@@ -129,7 +64,7 @@ const AppliedSearchSchema = z.object({
129
64
  state: z
130
65
  .string()
131
66
  .optional()
132
- .describe('The state filter applied. Restricts results to CO-OPS and excludes NDBC.'),
67
+ .describe("The state filter applied, matched against each station's resolved state: its own catalog code, or for a station publishing none the state of the nearest state-bearing CO-OPS tide or water-level station within 25 km. Restricts results to CO-OPS and excludes NDBC."),
133
68
  types: z
134
69
  .array(z.string().describe('A requested capability or platform filter value.'))
135
70
  .optional()
@@ -214,15 +149,13 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
214
149
  .min(-90)
215
150
  .max(90)
216
151
  .optional()
217
- .describe('Center latitude in decimal degrees for proximity search. Required together with longitude — ' +
218
- 'supplying only one is rejected rather than silently ignored. Optionally pair with radius_km.'),
152
+ .describe('Center latitude in decimal degrees for proximity search. Required together with longitude — supplying only one is rejected rather than silently ignored. Optionally pair with radius_km.'),
219
153
  longitude: z
220
154
  .number()
221
155
  .min(-180)
222
156
  .max(180)
223
157
  .optional()
224
- .describe('Center longitude in decimal degrees for proximity search. Required together with latitude — ' +
225
- 'supplying only one is rejected rather than silently ignored. Optionally pair with radius_km.'),
158
+ .describe('Center longitude in decimal degrees for proximity search. Required together with latitude — supplying only one is rejected rather than silently ignored. Optionally pair with radius_km.'),
226
159
  radius_km: z
227
160
  .number()
228
161
  .min(1)
@@ -232,16 +165,11 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
232
165
  query: z
233
166
  .string()
234
167
  .optional()
235
- .describe('Station name or station ID substring to match, case-insensitive, on both sources. ' +
236
- 'E.g. "seattle", "puget sound", "9447130", "46041". A station whose ID matches exactly is ' +
237
- 'returned ahead of name matches, unless latitude/longitude were supplied — a proximity ' +
238
- 'search orders by distance instead. Blank or whitespace-only values are treated as omitted.'),
168
+ .describe('Station name or station ID substring to match, case-insensitive, on both sources. E.g. "seattle", "puget sound", "9447130", "46041". A station whose ID matches exactly is returned ahead of name matches, unless latitude/longitude were supplied — a proximity search orders by distance instead. Blank or whitespace-only values are treated as omitted.'),
239
169
  state: z
240
170
  .enum(STATE_CODES)
241
171
  .optional()
242
- .describe('Filter by 2-letter US state or territory code. Applies to CO-OPS stations only — ' +
243
- 'providing it restricts results to CO-OPS and excludes NDBC buoys (which carry no state). ' +
244
- 'E.g. "WA", "CA", "PR".'),
172
+ .describe('Filter by 2-letter US state or territory code. Applies to CO-OPS stations only — providing it restricts results to CO-OPS and excludes NDBC buoys (which carry no state). A station matches on its own catalog code, or — when its catalog rows carry none, as with every current station — on the state of the nearest state-bearing CO-OPS tide or water-level station within 25 km, which can be wrong on waters shared across a state or national border. E.g. "WA", "CA", "PR".'),
245
173
  source: z
246
174
  .enum(['coops', 'ndbc', 'all'])
247
175
  .default('all')
@@ -257,20 +185,9 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
257
185
  'water_quality',
258
186
  'buoy',
259
187
  ])
260
- .describe("Filter value. Six are data capabilities, matched against a station's capabilities list: " +
261
- 'tide (CO-OPS tide predictions → noaa_marine_get_tide_predictions), ' +
262
- 'current (CO-OPS tidal-current predictions → noaa_marine_get_currents), ' +
263
- 'water_level (CO-OPS observed water levels → noaa_marine_get_water_level), ' +
264
- 'met (NDBC meteorological → noaa_marine_get_conditions), ' +
265
- 'current_profile (NDBC observed ocean-current depth profile → noaa_marine_get_current_profile; ' +
266
- 'note this is a different data product and source than CO-OPS `current`), ' +
267
- 'water_quality (NDBC sub-surface water-column sensors → noaa_marine_get_ocean_observations). ' +
268
- 'The seventh, buoy, is a physical-platform filter (NDBC platform class equals buoy), not a data ' +
269
- 'capability — use it to select buoy-class platforms regardless of what data they serve.'))
188
+ .describe("Filter value. Six are data capabilities, matched against a station's capabilities list: tide (CO-OPS tide predictions → noaa_marine_get_tide_predictions), current (CO-OPS tidal-current predictions → noaa_marine_get_currents), water_level (CO-OPS observed water levels → noaa_marine_get_water_level, and their verified monthly means → noaa_marine_get_monthly_means), met (NDBC meteorological → noaa_marine_get_conditions), current_profile (NDBC observed ocean-current depth profile → noaa_marine_get_current_profile; note this is a different data product and source than CO-OPS `current`), water_quality (NDBC sub-surface water-column sensors → noaa_marine_get_ocean_observations). The seventh, buoy, is a physical-platform filter (NDBC platform class equals buoy), not a data capability — use it to select buoy-class platforms regardless of what data they serve."))
270
189
  .optional()
271
- .describe('Filter by data capability or NDBC platform class. Every returned station matches at least one requested ' +
272
- 'value — a capability value against its capabilities list, or buoy against its platform class. ' +
273
- 'Omit to return all stations.'),
190
+ .describe('Filter by data capability or NDBC platform class. Every returned station matches at least one requested value — a capability value against its capabilities list, or buoy against its platform class. Omit to return all stations.'),
274
191
  limit: z
275
192
  .number()
276
193
  .int()
@@ -291,17 +208,11 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
291
208
  type: z
292
209
  .string()
293
210
  .optional()
294
- .describe('The data capability this row leads with — always one of the values in capabilities. When a types ' +
295
- 'filter of capability values is set this is the first requested capability the station has, so it ' +
296
- 'never contradicts the filter; otherwise it is the first capability. Omitted when the station has ' +
297
- 'no data capability (e.g. a bare buoy/fixed platform matched only by a platform filter) — read ' +
298
- 'platform for its identity. This is a data-product axis, never the physical platform class.'),
211
+ .describe('The data capability this row leads with — always one of the values in capabilities. When a types filter of capability values is set this is the first requested capability the station has, so it never contradicts the filter; otherwise it is the first capability. Omitted when the station has no data capability (e.g. a bare buoy/fixed platform matched only by a platform filter) — read platform for its identity. This is a data-product axis, never the physical platform class.'),
299
212
  platform: z
300
213
  .string()
301
214
  .optional()
302
- .describe('NDBC physical platform class: buoy, fixed, oilrig, dart, tao, usv, or other. A different axis than ' +
303
- 'type/capabilities (which describe data products). Omitted for CO-OPS stations — CO-OPS publishes ' +
304
- 'no platform taxonomy.'),
215
+ .describe('NDBC physical platform class: buoy, fixed, oilrig, dart, tao, usv, or other. A different axis than type/capabilities (which describe data products). Omitted for CO-OPS stations — CO-OPS publishes no platform taxonomy.'),
305
216
  latitude: z.number().describe('Station latitude in decimal degrees.'),
306
217
  longitude: z.number().describe('Station longitude in decimal degrees.'),
307
218
  distance_km: z
@@ -311,12 +222,14 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
311
222
  state: z
312
223
  .string()
313
224
  .optional()
314
- .describe('US state or territory code (CO-OPS stations only).'),
225
+ .describe("US state or territory code (CO-OPS stations only): the station's own catalog code, or — when its catalog rows carry none, as with every current station — the state of the nearest state-bearing CO-OPS tide or water-level station within 25 km, marked by state_derived. A derived state can be wrong on waters shared across a state or national border. When neither applies, the station's own non-code catalog value (e.g. FM) is shown as published and unmarked — no state filter returns such a station, since the filter takes codes only. Omitted when the station publishes nothing at all and no state-bearing station is that close."),
226
+ state_derived: z
227
+ .boolean()
228
+ .optional()
229
+ .describe("True when state was derived from the nearest state-bearing CO-OPS tide or water-level station within 25 km, because the station's own catalog rows carry no state code. Omitted when state is the station's own."),
315
230
  capabilities: z
316
231
  .array(z.string().describe('Capability identifier, e.g. "tide", "water_level".'))
317
- .describe('Data products available at this station: any of tide, current, water_level (CO-OPS) or met, ' +
318
- 'current_profile, water_quality (NDBC). Empty when the station reports no data capability — ' +
319
- 'platform still identifies it.'),
232
+ .describe('Data products available at this station: any of tide, current, water_level (CO-OPS) or met, current_profile, water_quality (NDBC). Empty when the station reports no data capability — platform still identifies it.'),
320
233
  prediction_class: z
321
234
  .string()
322
235
  .optional()
@@ -476,6 +389,11 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
476
389
  // Process CO-OPS stations
477
390
  if (coopsResults.status === 'fulfilled' && coopsResults.value) {
478
391
  const [tideStations, currentStations, waterLevelStations] = coopsResults.value;
392
+ const states = coopsSvc.stationStates({
393
+ tide: tideStations,
394
+ current: currentStations,
395
+ waterLevel: waterLevelStations,
396
+ });
479
397
  // Build unique station map — a station can appear in multiple lists, and each list
480
398
  // it appears in IS one of its capabilities, so membership needs no separate index.
481
399
  const allCoops = new Map();
@@ -515,7 +433,8 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
515
433
  for (const s of allCoops.values()) {
516
434
  if (!matchesTypeFilter(s.capabilities))
517
435
  continue;
518
- if (input.state && s.state !== input.state)
436
+ const resolved = states.get(s.id);
437
+ if (input.state && resolved?.state !== input.state)
519
438
  continue;
520
439
  if (!matchesQuery(s.name, s.id))
521
440
  continue;
@@ -530,8 +449,11 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
530
449
  const coopsType = primaryTypeFor(s.capabilities);
531
450
  if (coopsType)
532
451
  entry.type = coopsType;
533
- if (s.state)
534
- entry.state = s.state;
452
+ if (resolved) {
453
+ entry.state = resolved.state;
454
+ if (resolved.derived)
455
+ entry.state_derived = true;
456
+ }
535
457
  const tideRow = tideRowById.get(s.id);
536
458
  const predictionClass = tidePredictionClass(tideRow?.type);
537
459
  if (predictionClass)
@@ -616,7 +538,7 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
616
538
  const stations = results.slice(0, input.limit);
617
539
  const truncated = stations.length < total_found;
618
540
  // One notice reaches the caller, so every source of guidance composes into it —
619
- // ctx.enrich.truncated() writes `notice` last-wins and would otherwise erase the rest.
541
+ // ctx.enrich.notice() writes `notice` last-wins and would otherwise erase the rest.
620
542
  const notices = [];
621
543
  if (failed.length > 0) {
622
544
  const report = { answered, attempted, failed };
@@ -644,20 +566,13 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
644
566
  ctx.enrich({ applied_search: applied });
645
567
  notices.push(describeEmptySearch(applied));
646
568
  }
569
+ // The cap itself is on output as `truncated`, beside `stations.length` and `total_found`;
570
+ // the notice carries the guidance for reaching the rest.
647
571
  if (truncated) {
648
- ctx.enrich.truncated({
649
- shown: stations.length,
650
- cap: input.limit,
651
- ceiling: total_found,
652
- guidance: [
653
- `Showing ${stations.length} of ${total_found} matches — raise limit (max 200) or narrow the filters to reach the rest.`,
654
- ...notices,
655
- ].join(' '),
656
- });
572
+ notices.unshift(`Showing ${stations.length} of ${total_found} matches — raise limit (max 200) or narrow the filters to reach the rest.`);
657
573
  }
658
- else if (notices.length > 0) {
574
+ if (notices.length > 0)
659
575
  ctx.enrich.notice(notices.join(' '));
660
- }
661
576
  ctx.log.info('Station search complete', {
662
577
  total_found,
663
578
  returned: stations.length,
@@ -677,7 +592,9 @@ export const noaaMarineFindStations = tool('noaa_marine_find_stations', {
677
592
  const lines = [header];
678
593
  for (const s of result.stations) {
679
594
  const dist = s.distance_km !== undefined ? ` · ${s.distance_km} km` : '';
680
- const state = s.state ? ` · ${s.state}` : '';
595
+ const state = s.state
596
+ ? ` · ${s.state}${s.state_derived ? ' (derived from the nearest state-bearing station)' : ''}`
597
+ : '';
681
598
  const typeStr = s.type ? ` · **Type:** ${s.type}` : '';
682
599
  const platformStr = s.platform ? ` · **Platform:** ${s.platform}` : '';
683
600
  const classStr = s.prediction_class ? ` · **Prediction class:** ${s.prediction_class}` : '';