Skip to main content
Glama
@cyanheads/noaa-marine-mcp-server

@cyanheads/noaa-marine-mcp-server

by cyanheads

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://noaa-marine.caseyjhand.com/mcp


Overview

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.

Tools

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

Resources

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.

Agent skill

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.

Related MCP server: mcp-noaa-tides

Capability reference

noaa_marine_find_stations tool

  • Filters combine: 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, and a larger value is clamped to 200 with a notice (rejected when NOAA_CLAMP_LIMITS=false); offset pages past one page

  • Each station carries source, 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 class

  • total_found counts every match across pages; truncated and next_offset appear while matches remain past the page; 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_coordinates


noaa_marine_get_tide_predictions tool

  • 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_level

  • Other failures: invalid_date_range, date_range_exceeded, station_not_found, datum_unavailable (its hint names a datum to retry with, never the one just rejected), and upstream_throttled, a retryable CO-OPS HTTP 403 that clears in a couple of minutes


noaa_marine_get_water_level tool

  • 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)

  • Observations carry 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_dropped

  • Paired predictions at the matching cadence (none on daily_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_gauge, future_window (begin_date after the station's current date in the requested time_zone), 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_throttled


noaa_marine_get_monthly_means tool

  • Up to 73,000 days per request; eleven datums (MLLW, MHHW, MHW, MTL, MSL, MLW, NAVD, STND, IGLD, LWD, CRD) in english feet or metric meters; the first and last months returned are those begin_date and end_date fall in

  • One row per month: highest, 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 lowest

  • Failures: invalid_date_range, date_range_exceeded, station_not_found, datum_unavailable, no_gauge, 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 otherwise


noaa_marine_get_currents tool

  • 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)

  • Every response echoes bin and depth; rows carry a flood / ebb / slack type, a non-negative speed, and as direction the station's mean bearing for that flow

  • A bin the station lacks is bin_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_throttled


noaa_marine_get_conditions tool

  • One NDBC station_id; returns wind, waves, sea-surface and air temperature, dew point, and pressure in SI units, except tide_ft and visibility_nmi

  • A 7-digit CO-OPS station ID also works when NDBC relays that gauge's met sensors under a station of its own: station_id is then the NDBC station read, the requested ID comes back as coops_station_id, and notice names both

  • Every sensor field is nullable; each column block comes from the newest row within 90 minutes that carried it, waves reporting their own waves_observed_at and any other older block named in notice. Failures: buoy_not_found (no observation file and no relay, a relay with no file, or several relays the met flag doesn't settle), no_sensor_data


noaa_marine_get_current_profile tool

  • One NDBC 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 unreported

  • Observed ADCP measurements, not the CO-OPS predictions noaa_marine_get_currents returns; failures are profile_not_found (no ADCP file) and no_current_data


noaa_marine_get_ocean_observations tool

  • One NDBC 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 potential

  • Most stations report only temperature and salinity, and unreported values are null; failures are observations_not_found (no .ocean file) and no_ocean_data


noaa-marine://station/{station_id} resource

  • 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 cacheHint

  • station_not_found only when both catalogs were read; source_unavailable when one could not be

Features

Built 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:

  • CO-OPS data and metadata APIs plus the NDBC active-stations catalog and realtime2 feeds (.txt, .adcp, .ocean), with station catalogs cached in memory for 6 hours; CO-OPS requests carry application= from NOAA_APPLICATION_ID

  • Station IDs belong to one source, with one exception: CO-OPS tide and water-level IDs are numeric (9447130), CO-OPS current IDs alphanumeric (ACT4176), NDBC IDs mostly 5-character codes (46041) with some 7-digit numbers, and only noaa_marine_get_conditions reads a CO-OPS ID, through its NDBC relay

  • CO-OPS tools take 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, a datum a station lacks is datum_unavailable with a hint naming a datum to retry with, never the rejected one, and a station that has never had a water-level gauge is no_gauge

  • CO-OPS verifies hourly, 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_data

  • CO-OPS answers a burst of requests with HTTP 403 for about two minutes; the CO-OPS data tools report it as a retryable upstream_throttled whose recovery names the wait

Agent-friendly output:

  • Count paging on the four CO-OPS series tools: a page is limit rows from offset, defaulting to 50 (24 for monthly means) with a larger value clamped to 1,000 and a notice; a range of at most limit rows returns whole, and a longer one adds rows_matched, rows_returned, page_offset, and next_offset

  • Missing means null or omitted, never fabricated: NDBC MM markers become null, blank CO-OPS values are dropped, and quality is left out rather than defaulted

  • Discriminated fields: station source (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 parsing

Getting started

Connect to the hosted instance, no install:

claude mcp add --transport http noaa-marine-mcp-server https://noaa-marine.caseyjhand.com/mcp

Or run it locally:

claude mcp add noaa-marine-mcp-server -- npx -y @cyanheads/noaa-marine-mcp-server

For 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.

Configuration

Variable

Description

Default

NOAA_APPLICATION_ID

Courtesy identifier sent as application= on CO-OPS requests.

noaa-marine-mcp-server

NOAA_CLAMP_LIMITS

Clamp a limit above its cap (1,000 series rows, 200 stations) to the cap with a notice. false enforces each cap as a schema maximum.

true

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.

Running the server

Local development

Requires Bun v1.4.0 or higher (or Node.js v24+).

  • Clone and install:

    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
  • Build and run:

    bun run rebuild
    
    bun run start:stdio
    # or
    bun run start:http
  • Run checks and tests:

    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec

Docker

docker run --rm -p 3010:3010 ghcr.io/cyanheads/noaa-marine-mcp-server:latest

The 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.

Project structure

Path

Purpose

src/index.ts

createApp() entry point: registers the tools and resource, initializes services.

src/config/

NOAA_APPLICATION_ID and NOAA_CLAMP_LIMITS 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.

Development guide

See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic

  • Use ctx.log for request-scoped logging

  • Register tools and resources in src/index.ts directly (no barrels for this server)

  • Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields

  • NDBC MM values must normalize to null, not be passed through as strings

Contributing

Issues are welcome. Run checks and tests before submitting:

bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Related MCP Connectors

Related MCP Servers