About noaa-marine-mcp-server
An MCP server for U.S. coastal and marine observations from NOAA CO-OPS and NDBC. It helps users locate stations and retrieve tide, water-level, current, and buoy data.
Key features
- 01Find CO-OPS stations and NDBC buoys
- 02Retrieve tide predictions and water levels
- 03Get tidal-current predictions and profiles
- 04Fetch live buoy weather and sea conditions
- 05Read ocean water-column observations
Use cases
- 01Check predicted tides at a coastal station
- 02Compare observed water levels with predictions
- 03Review buoy wind, wave, and temperature readings
- 04Inspect currents by depth or water quality
MCP support
Exposes eight tools and a station metadata resource to MCP clients. Supports STDIO and Streamable HTTP transports.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://noaa-marine.caseyjhand.com/mcpclaude mcp add --transport http cyanheads-noaa-marine https://noaa-marine.caseyjhand.com/mcp{"mcpServers":{"cyanheads-noaa-marine":{"type":"http","url":"https://noaa-marine.caseyjhand.com/mcp"}}}Open in Cursor{"servers":{"cyanheads-noaa-marine":{"type":"http","url":"https://noaa-marine.caseyjhand.com/mcp"}}}Open in VS Code{"mcpServers":{"cyanheads-noaa-marine":{"type":"http","url":"https://noaa-marine.caseyjhand.com/mcp"}}}{"mcpServers":{"cyanheads-noaa-marine":{"serverUrl":"https://noaa-marine.caseyjhand.com/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
8| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Maximum number of stations to return. Defaults to 20. |
| query | string | No | 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. |
| state | enum | No | 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".AL | AK | AZ | AR | CA | CO | CT | DE | FL | GA | HI | ID | IL | IN | IA | KS | KY | LA | ME | MD | MA | MI | MN | MS | MO | MT | NE | NV | NH | NJ | NM | NY | NC | ND | OH | OK | OR | PA | RI | SC | SD | TN | TX | UT | VT | VA | WA | WV | WI | WY | DC | PR | VI | GU | AS | MP |
| types | array | No | 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. |
| source | enum | No | Data source to search: coops (tide/water-level/current stations), ndbc (buoys), or all (default).coops | ndbc | all |
| latitude | number | No | 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. |
| longitude | number | No | 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. |
| radius_km | number | No | Search radius in kilometers when latitude/longitude are provided. Defaults to 100 km. |
Details
From the project README@cyanheads/noaa-marine-mcp-server
Find NOAA tide stations and NDBC buoys, fetch tide predictions, water levels, tidal currents, and live buoy conditions via MCP. STDIO or Streamable HTTP.
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.
Capability reference
noaa_marine_find_stations tool
- Filters combine:
latitude+longitudewithradius_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), andtypes(tide,current,water_level,met,current_profile,water_quality, or the platform classbuoy);limitdefaults to 20, max 200 - Each station carries
source,capabilities,type, and for NDBC aplatform; CO-OPS rows addstate(state_derived: truewhen borrowed from the nearest state-bearing station within 25 km), a tideprediction_class(reference, orsubordinatewithreference_id), and currentbins[]giving each bin's number, depth in feet, and class total_foundandtruncatedreport the count beforelimit; zero matches is a success withapplied_search, a partial catalog read is flagged insources, and total catalog failure issources_unavailable; only one oflatitude/longitudeisincomplete_coordinates
noaa_marine_get_tide_predictions tool
intervalhilo(default) or6min, up to 365 days; ten datums (MLLW, MHHW, MHW, MTL, MSL, MLW, DTL, NAVD, STND, CRD),time_zone(lst_ldt,gmt,lst), andunits(englishfeet,metricmeters)6minworks at reference stations only: a subordinate station fails assubordinate_no_6minnaming its reference station, and a Great Lakes station isno_predictions, pointing tonoaa_marine_get_water_level- Other failures:
invalid_date_range,date_range_exceeded,station_not_found,datum_unavailable(naming the planes the station carries), andupstream_throttled, a retryable CO-OPS HTTP 403 that clears in a couple of minutes
noaa_marine_get_water_level tool
interval6min(default, 31 days),hourlyorhigh_low(365 days), ordaily_mean(3,655 days, Great Lakes only, always in LST; a coastal station isgreat_lakes_only); thirteen datums (MLLW, MHHW, MHW, MTL, MSL, MLW, NAVD, STND, IGLD, LWD, CRD, LWI, HWI)- Observations carry
quality(p/v,6minonly),sigma(6minandhourly), andtype(H/HH/L/LL,high_lowonly); slots with no reading are dropped and counted ingaps_dropped - Paired predictions at the matching cadence (none on
daily_mean), withpredictions_status(empty/unavailable) explaining an empty series;residual_summary(max_surge,max_drawdown) covers the whole matched range, on6minandhourlyonly. 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), andupstream_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
englishfeet ormetricmeters; the first and last months returned are thosebegin_dateandend_datefall 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 theinferredcode verbatim; values CO-OPS leaves blank are omitted, so a Great Lakes station carries onlyhighest,msl, andlowest - Failures:
invalid_date_range,date_range_exceeded,station_not_found,datum_unavailable,upstream_throttled, and for an empty windowverified_data_lagwhen it ends on or after the first day of the prior month,no_dataotherwise
noaa_marine_get_currents tool
intervalMAX_SLACK(default: max flood, max ebb, and slack events) or6min, up to 365 days;binpicks a depth bin frombins[](default the shallowest);unitsenglish(knots, feet) ormetric(cm/s, meters)- Every response echoes
binanddepth; rows carry aflood/ebb/slacktype, a non-negativespeed, and asdirectionthe station's mean bearing for that flow - A bin the station lacks is
bin_unavailablelisting the ones it has; a station CO-OPS won't predict as events returns an empty list with its statement innotice, and one with no published predictions ispredictions_unavailable; alsoinvalid_date_range,date_range_exceeded,station_not_found,no_predictions, andupstream_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, excepttide_ftandvisibility_nmi - 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_atand any other older block named innotice. Failures:buoy_not_found,no_sensor_data
noaa_marine_get_current_profile tool
- One NDBC
station_id, found withtypes: ["current_profile"];bins[]runs shallowest first withdepth_m,direction_deg(true, flow-toward), andspeed_cm_s, the last two null when unreported - Observed ADCP measurements, not the CO-OPS predictions
noaa_marine_get_currentsreturns; failures areprofile_not_found(no ADCP file) andno_current_data
noaa_marine_get_ocean_observations tool
- One NDBC
station_id, found withtypes: ["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 areobservations_not_found(no.oceanfile) andno_ocean_data
noaa-marine://station/{station_id} resource
station_idfromnoaa_marine_find_stations; returns that tool's station record asapplication/json, plusownerfor NDBC stations, with a 6-hourcacheHintstation_not_foundonly when both catalogs were read;source_unavailablewhen 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
realtime2feeds (.txt,.adcp,.ocean), with station catalogs cached in memory for 6 hours; CO-OPS requests carryapplication=fromNOAA_APPLICATION_ID - Station IDs never cross sources: CO-OPS tide and water-level IDs are numeric (
9447130), CO-OPS current IDs alphanumeric (ACT4176), NDBC IDs 5-character codes (46041) - CO-OPS tools take
begin_date/end_dateasYYYYMMDDorYYYY-MM-DDand 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 isdatum_unavailablenaming ones it carries - 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 isverified_data_lag, an earlier oneno_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_throttledwhose recovery names the wait
Agent-friendly output:
- Byte-bounded paging on the four CO-OPS series tools: a range that fits returns whole, and a longer one returns a leading page with
rows_matched,rows_returned,page_offset, andnext_offset, walked withoffset(limitshrinks a page) - Missing means
nullor omitted, never fabricated: NDBCMMmarkers becomenull, blank CO-OPS values are dropped, andqualityis left out rather than defaulted - Discriminated fields: station
source(coops|ndbc), capabilitytype, NDBCplatform, andprediction_class, with datum and units echoed on every CO-OPS height response, so callers branch on data, not string parsing
Getting started
Public Hosted Instance
A public instance is available at https://noaa-marine.caseyjhand.com/mcp, with no installation or API key. It serves Streamable HTTP; each client takes its own config shape.
For Claude Code:
claude mcp add --transport http noaa-marine-mcp-server https://noaa-marine.caseyjhand.com/mcp
For Cline (cline_mcp_settings.json):
{
"mcpServers": {
"noaa-marine-mcp-server": {
"type": "streamableHttp",
"url": "https://noaa-marine.caseyjhand.com/mcp"
}
}
}
For Cursor (mcp.json):
{
"mcpServers": {
"noaa-marine-mcp-server": {
"url": "https://noaa-marine.caseyjhand.com/mcp"
}
}
}
Claude Desktop adds remote servers under Settings → Connectors.
Self-Hosted / Local
Add the following to your MCP client configuration file:
{
"mcpServers": {
"noaa-marine-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/noaa-marine-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"noaa-marine-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/noaa-marine-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"noaa-marine-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/noaa-marine-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key: NOAA CO-OPS and NDBC are open data sources.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/noaa-marine-mcp-server.git
- Navigate into the directory:
cd noaa-marine-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if needed (all vars optional)
Configuration
| 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.
Running the server
Local development
-
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 build -t noaa-marine-mcp-server .
docker run --rm -p 3010:3010 noaa-marine-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/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 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/catchin tool logic - Use
ctx.logfor request-scoped logging - Register tools and resources in
src/index.tsdirectly (no barrels for this server) - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
- NDBC
MMvalues must normalize tonull, 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.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.