.codex-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .codex-plugin (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/noaa-marine-mcp-server</h1> <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> <div>8 Tools • 1 Resource • 1 Skill</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://noaa-marine.caseyjhand.com/mcp
</div>
US tides, currents, water levels, and buoy observations from NOAA CO-OPS and NDBC. Find coastal stations and buoys, then fetch tide and tidal-current predictions, observed and monthly-mean water levels, and live buoy conditions, current profiles, and water-column readings.
| Tool | Description |
|---|---|
noaa_marine_find_stations | Find CO-OPS tide, water-level, and current stations and NDBC buoys by location, name, ID, state, or capability |
noaa_marine_get_tide_predictions | High/low tide predictions or a 6-minute curve for a CO-OPS tide station |
noaa_marine_get_water_level | Observed water level at four cadences, paired with predictions and a storm-surge residual |
noaa_marine_get_monthly_means | Verified monthly tidal datums and extremes for a CO-OPS water-level station |
noaa_marine_get_currents | CO-OPS tidal-current predictions: max flood, max ebb, and slack events, or a 6-minute curve |
noaa_marine_get_conditions | Live NDBC buoy conditions: waves, wind, sea-surface and air temperature, pressure |
noaa_marine_get_current_profile | Observed ocean-current depth profile from an NDBC ADCP buoy |
noaa_marine_get_ocean_observations | Sub-surface water-column readings (temperature, salinity, oxygen, and more) from an NDBC station |
| Resource | Description |
|---|---|
noaa-marine://station/{station_id} | Metadata for a CO-OPS or NDBC station: name, coordinates, source, capabilities, prediction class, platform |
Everything in the resource except NDBC owner is also on noaa_marine_find_stations rows, for clients that only call tools.
The published noaa-marine-guide skill carries the gates and conventions the tool descriptions don't: which station serves which tool, why a place-name search misses, the clock and direction differences between CO-OPS and NDBC, and what the server can't answer (forecasts, warnings, past buoy readings). MCP clients read it as skill://noaa-marine-guide/SKILL.md through the Skills extension. Plugin installs include it too.
noaa_marine_find_stations <sub>tool</sub>latitude + longitude with radius_km (default 100, max 1000), query (name or ID substring on both sources; an exact ID leads unless sorting by distance), state (CO-OPS only), source (coops / ndbc / all), and types (tide, current, water_level, met, current_profile, water_quality, or the platform class buoy); limit defaults to 20, max 200source, capabilities, type, and for NDBC a platform; CO-OPS rows add state (state_derived: true when borrowed from the nearest state-bearing station within 25 km), a tide prediction_class (reference, or subordinate with reference_id), and current bins[] giving each bin's number, depth in feet, and classtotal_found and truncated report the count before limit; zero matches is a success with applied_search, a partial catalog read is flagged in sources, and total catalog failure is sources_unavailable; only one of latitude/longitude is incomplete_coordinatesnoaa_marine_get_tide_predictions <sub>tool</sub>interval hilo (default) or 6min, up to 365 days; ten datums (MLLW, MHHW, MHW, MTL, MSL, MLW, DTL, NAVD, STND, CRD), time_zone (lst_ldt, gmt, lst), and units (english feet, metric meters)6min works at reference stations only: a subordinate station fails as subordinate_no_6min naming its reference station, and a Great Lakes station is no_predictions, pointing to noaa_marine_get_water_levelinvalid_date_range, date_range_exceeded, station_not_found, datum_unavailable (naming the planes the station carries), and upstream_throttled, a retryable CO-OPS HTTP 403 that clears in a couple of minutesnoaa_marine_get_water_level <sub>tool</sub>interval 6min (default, 31 days), hourly or high_low (365 days), or daily_mean (3,655 days, Great Lakes only, always in LST; a coastal station is great_lakes_only); thirteen datums (MLLW, MHHW, MHW, MTL, MSL, MLW, NAVD, STND, IGLD, LWD, CRD, LWI, HWI)quality (p / v, 6min only), sigma (6min and hourly), and type (H / HH / L / LL, high_low only); slots with no reading are dropped and counted in gaps_droppeddaily_mean), with predictions_status (empty / unavailable) explaining an empty series; residual_summary (max_surge, max_drawdown) covers the whole matched range, on 6min and hourly only. Failures: invalid_date_range, date_range_exceeded, station_not_found, datum_unavailable, no_data, verified_data_lag (a coarser-cadence window ending on or after the first day of the prior month, which CO-OPS may not have verified yet), and upstream_throttlednoaa_marine_get_monthly_means <sub>tool</sub>english feet or metric meters; the first and last months returned are those begin_date and end_date fall inhighest, lowest, the tidal datums (mhhw, mhw, msl, mtl, mlw, mllw, dtl), ranges (gt, mn, dhq, dlq), lunitidal intervals in hours (hwi, lwi), and the inferred code verbatim; values CO-OPS leaves blank are omitted, so a Great Lakes station carries only highest, msl, and lowestinvalid_date_range, date_range_exceeded, station_not_found, datum_unavailable, upstream_throttled, and for an empty window verified_data_lag when it ends on or after the first day of the prior month, no_data otherwisenoaa_marine_get_currents <sub>tool</sub>interval MAX_SLACK (default: max flood, max ebb, and slack events) or 6min, up to 365 days; bin picks a depth bin from bins[] (default the shallowest); units english (knots, feet) or metric (cm/s, meters)bin and depth; rows carry a flood / ebb / slack type, a non-negative speed, and as direction the station's mean bearing for that flowbin_unavailable listing the ones it has; a station CO-OPS won't predict as events returns an empty list with its statement in notice, and one with no published predictions is predictions_unavailable; also invalid_date_range, date_range_exceeded, station_not_found, no_predictions, and upstream_throttlednoaa_marine_get_conditions <sub>tool</sub>station_id; returns wind, waves, sea-surface and air temperature, dew point, and pressure in SI units, except tide_ft and visibility_nmiwaves_observed_at and any other older block named in notice. Failures: buoy_not_found, no_sensor_datanoaa_marine_get_current_profile <sub>tool</sub>station_id, found with types: ["current_profile"]; bins[] runs shallowest first with depth_m, direction_deg (true, flow-toward), and speed_cm_s, the last two null when unreportednoaa_marine_get_currents returns; failures are profile_not_found (no ADCP file) and no_current_datanoaa_marine_get_ocean_observations <sub>tool</sub>station_id, found with types: ["water_quality"], a catalog flag rather than a guarantee; readings[] holds one row per depth with temperature, conductivity, salinity, dissolved oxygen (% and ppm), chlorophyll, turbidity, pH, and redox potentialnull; failures are observations_not_found (no .ocean file) and no_ocean_datanoaa-marine://station/{station_id} <sub>resource</sub>station_id from noaa_marine_find_stations; returns that tool's station record as application/json, plus owner for NDBC stations, with a 6-hour cacheHintstation_not_found only when both catalogs were read; source_unavailable when one could not beBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
CO-OPS / NDBC-specific:
realtime2 feeds (.txt, .adcp, .ocean), with station catalogs cached in memory for 6 hours; CO-OPS requests carry application= from NOAA_APPLICATION_ID9447130), CO-OPS current IDs alphanumeric (ACT4176), NDBC IDs 5-character codes (46041)begin_date / end_date as YYYYMMDD or YYYY-MM-DD and check the calendar and each tool's range ceiling before calling (invalid_date_range, date_range_exceeded); heights default to MLLW, and a datum the station lacks is datum_unavailable naming ones it carrieshourly, high_low, daily_mean, and monthly means once a month, for the prior month: an empty window ending on or after the first of the prior month is verified_data_lag, an earlier one no_dataupstream_throttled whose recovery names the waitAgent-friendly output:
rows_matched, rows_returned, page_offset, and next_offset, walked with offset (limit shrinks a page)null or omitted, never fabricated: NDBC MM markers become null, blank CO-OPS values are dropped, and quality is left out rather than defaultedsource (coops | ndbc), capability type, NDBC platform, and prediction_class, with datum and units echoed on every CO-OPS height response, so callers branch on data, not string parsingConnect to the hosted instance, no install:
claude mcp add --transport http noaa-marine-mcp-server https://noaa-marine.caseyjhand.com/mcpOr run it locally:
claude mcp add noaa-marine-mcp-server -- npx -y @cyanheads/noaa-marine-mcp-serverFor other clients, add this to the MCP config file:
{
"mcpServers": {
"noaa-marine-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/noaa-marine-mcp-server"]
}
}
}No API key: NOAA CO-OPS and NDBC are open data sources. For Docker or a source build, see Running the server.
| Variable | Description | Default |
|---|---|---|
NOAA_APPLICATION_ID | Courtesy identifier sent as application= on CO-OPS requests. | noaa-marine-mcp-server |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_HTTP_HOST | HTTP bind address. | 127.0.0.1 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result (redacted by key name only), capped by LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES. | false |
OTEL_ENABLED | Enable OpenTelemetry. | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL; traces and metrics export to /v1/traces and /v1/metrics under it. | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Opt-in OTLP log export endpoint; the base URL never enables it. | — |
See .env.example for the full list of optional overrides.
Requires Bun v1.4.0 or higher (or Node.js v24+).
git clone https://github.com/cyanheads/noaa-marine-mcp-server.git
cd noaa-marine-mcp-server && bun install
cp .env.example .env # all vars optional bun run rebuild
bun run start:stdio
# or
bun run start:http bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against specdocker run --rm -p 3010:3010 ghcr.io/cyanheads/noaa-marine-mcp-server:latestThe image defaults to HTTP transport, stateless session mode, and logs to /var/log/noaa-marine-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/noaa-marine-mcp-server:latest"]. To build locally, docker build -t noaa-marine-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Path | Purpose |
|---|---|
src/index.ts | createApp() entry point: registers the tools and resource, initializes services. |
src/config/ | NOAA_APPLICATION_ID env var parsing with Zod. |
src/services/coops/ | CO-OPS client: station catalog cache, data fetch, error classification, date-range validation, row paging, state resolution. |
src/services/ndbc/ | NDBC service: active-stations XML and realtime2 feed parsers. |
src/services/geo.ts | Haversine distance for proximity search and state resolution. |
src/mcp-server/tools/definitions/ | Eight tool definitions (*.tool.ts). |
src/mcp-server/resources/definitions/ | Station metadata resource (noaa-marine-station.resource.ts). |
tests/ | Vitest tests mirroring src/. |
docs/ | Design doc and directory tree. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped loggingsrc/index.ts directly (no barrels for this server)MM values must normalize to null, not be passed through as stringsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testApache-2.0 — see LICENSE for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.