@cyanheads/noaa-marine-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@cyanheads/noaa-marine-mcp-serverFind tide stations near Key West, FL"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Public Hosted Server: https://noaa-marine.caseyjhand.com/mcp
Overview
US tide, current, and buoy data from NOAA CO-OPS and NDBC. Find tide, water-level, and current stations plus NDBC buoys, then fetch tide predictions, observed water levels, tidal currents, and live buoy conditions from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
Tool | Description |
| Find CO-OPS tide/water-level/current stations and NDBC buoys by location, name, state, or data capability. |
| High/low tide predictions or a 6-minute curve for a CO-OPS tide station. |
| Observed water level at a 6-minute, hourly, high/low, or daily-mean cadence, paired with predictions and a storm-surge residual summary. |
| CO-OPS tidal current predictions — max flood/ebb/slack events or a 6-minute curve. |
| Live NDBC buoy conditions: waves, wind, sea-surface and air temperature, pressure. |
| Observed ocean-current depth profile from an NDBC ADCP buoy. |
| Sub-surface water-column observations (temperature, salinity, oxygen, and more) from an NDBC station. |
Resources
Resource | Description |
| Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, data capabilities, and — for NDBC — physical platform class. |
All resource data is also reachable via tools — use noaa_marine_find_stations to discover station IDs before accessing the resource.
Related MCP server: mcp-noaa-tides
Capability reference
noaa_marine_find_stations tool
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), ortypes: data capabilities (tide,current,water_level,met,current_profile,water_quality) or NDBC platform class (buoy)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)CO-OPS prediction stations also carry
prediction_class, a third axis beside capability and platform: a tide station isreference(servinghiloand the 6-minute curve) orsubordinate(hiloonly, withreference_idnaming where its offsets come from), while a current station reports its class per depth bin inbins[]alongside each bin's number and catalog depth in feet — the bin numbersnoaa_marine_get_currentstakes asbintotal_foundandtruncatedreport the full match count before the limit is appliedZero matches is a success with
total_found: 0, carrying a notice derived from the filters that were applied and an echo of the applied searchA catalog that fails to load is reported as an unread source alongside the results; when every needed catalog fails, that is a typed
sources_unavailableerror rather than an empty searchStation lists are cached in-memory with a 6-hour TTL — first call after startup may be slightly slower
Typed
incomplete_coordinateserror when only one of latitude/longitude is supplied
noaa_marine_get_tide_predictions tool
hilo(default, high/low events) or6mincontinuous curve; up to 1 year per requestA 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, andnext_offseton both consumption surfaces. Walk it withoffset;limitlowers a page and never raises it past the byte bound, and anoffsetpast the last row is an empty page rather than an error6minis served by reference stations only — a subordinate station's high and low events are offsets from a reference station and it has no 6-minute curve, so the request is refused before the upstream call as a typedsubordinate_no_6minnaminghiloand that reference stationTen datums, matching what the CO-OPS predictions product accepts: MLLW (default, US nautical chart), MHHW, MHW, MTL, MSL, MLW, DTL, NAVD (NAVD88, where the station has a tie), STND (the station's own datum), CRD (Columbia River only)
A datum the station does not carry is a typed
datum_unavailablenaming the planes it does, not a report that the station ID was wrong; a Great Lakes station, which publishes no prediction series at any datum, isno_predictionspointing atnoaa_marine_get_water_levelTime zone (
lst_ldtdefault,gmt,lst) and units (englishdefault feet,metricmeters)Typed
date_range_exceeded,invalid_date_range,station_not_found,no_predictions,datum_unavailable, andsubordinate_no_6minerrors
noaa_marine_get_water_level tool
intervalselects the cadence:6min(default) the full curve,hourlyhourly heights,high_lowthe observed high and low waters with theirH/HH/L/LLclassification,daily_meanthe daily mean at Great Lakes stations only. Theintervalis echoed in the outputPer-interval CO-OPS range ceilings, rejected locally before the call: 31 days for
6min, 365 forhourlyandhigh_low, 3,655 fordaily_mean. A coarser cadence is not automatically a smaller response — a year ofhourlyrows outweighs a month of 6-minute ones — so the ceiling bounds the request and the response budget bounds the pageQuality flags (
ppreliminary,vverified) on6minonly: CO-OPS sends no flag with the coarser products, andqualityis omitted rather than defaulted to preliminary, which would label verified data unverified. Sensorsigmaon6minandhourlyThirteen datums, matching what the CO-OPS water-level product accepts: MLLW (default, US nautical chart), MHHW, MHW, MTL, MSL, MLW, NAVD (NAVD88, where the station has a tie), STND (the station's own datum), IGLD and LWD (Great Lakes only), CRD (Columbia River only), LWI and HWI (lunitidal intervals)
A datum the station does not carry is a typed
datum_unavailablewhose recovery names the planes that do read it —STND,IGLD,LWDat a Great Lakes station,MLLW/STNDwhere an NAVD88 tie is missing — rather than sending the caller back to re-verify an IDnoaa_marine_find_stationsjust returnedA sensor outage leaves slots with no reading; they are dropped and counted in
gaps_dropped, sorows_matchedalways counts only the slots that carried a value and continuous coverage across the range is only implied when that count is absentPaired tide predictions at the interval matching the observed cadence, fetched in parallel — the observed series returns either way, and
predictions_statussays whether an empty prediction series means CO-OPS has none or the fetch failed. Not fetched at all ondaily_mean, which has no paired seriesresidual_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. Reported on6minandhourlyonly — observed high and low waters do not occur at the predicted extreme times, so ahigh_lowjoin would rest on a small fraction of the events, anddaily_meanhas no paired series at all; the notice says which appliesA 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, andnext_offseton both consumption surfaces. Observations carry theoffsetand the paired predictions follow by time window, so a page's two series always describe one span even after gap rows shorten the observed onedaily_meanis requested in local standard time whatevertime_zonewas passed — CO-OPS serves that product in LST only and silently shifts any other zone by a dayTyped
date_range_exceeded,invalid_date_range,station_not_found,no_data,datum_unavailable,great_lakes_only(daily_meanat a coastal station), andverified_data_lagerrors — the last for a window CO-OPS has not verified yet, since it verifies the coarser products monthly for the prior month
noaa_marine_get_currents tool
MAX_SLACK(default): max flood, max ebb, and slack events only — the actionable view for passage planning6min: continuous current curve, each row carrying its ownflood/ebb/slacksense plus the station mean flood or ebb bearing that sense implies (a station constant, not an instantaneous heading)Both intervals are bounded by response size: 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, andnext_offseton both surfaces.offsetandlimitwalk whichever series the interval selects — the max/slack events or the 6-minute curveUnits
english(knots for speed, feet for the echoed depth, default) ormetric(cm/s for speed, meters for depth) — CO-OPS publishes metric current speed in cm/s, the unitnoaa_marine_get_current_profilealso reportsbinselects one of a station's depth bins; omit it for the CO-OPS default, the shallowest. The bin CO-OPS answered with and its depth are echoed on every response, and a bin the station does not publish is a typedbin_unavailablenaming the bins it doesA station whose currents CO-OPS will not predict as discrete events returns an empty list plus CO-OPS's own wording in the notice, not an error
Current station IDs are alphanumeric (e.g.
ACT4176), distinct from numeric tide/water-level IDsUp to 1 year per request; typed
date_range_exceeded,invalid_date_range,station_not_found,no_predictions,predictions_unavailable, andbin_unavailableerrors
noaa_marine_get_conditions tool
Wave height/period/direction, wind speed/gust/direction, sea-surface and air temperature, dew point, barometric pressure
All values SI except
tide_ft(feet) andvisibility_nmi(nautical miles), both rarely populated at offshore buoysEvery sensor field is nullable —
nullwhen the buoy did not report, never a fabricated value.latitude/longitudearenullfor a station absent from the NDBC catalog, andobserved_atis always a valid instantNDBC writes each block of columns on its own cycle, so a block resolves from the most recent row within 90 minutes that carried it: waves report their own
waves_observed_at, and any other block read from an earlier row is named with its measurement time in the response notice. Row cadence runs 5–60 minutes depending on the stationTyped
buoy_not_foundandno_sensor_dataerrors
noaa_marine_get_current_profile tool
Depth (m), direction (degrees true, flow-toward), and speed (cm/s) per bin, shallowest first
Observed NDBC ADCP measurement — distinct from
noaa_marine_get_currents, a CO-OPS tidal-current predictionMost NDBC stations serve no ADCP profile; use
find_stationswithtypes: ["current_profile"]to discover ones that doDirection or speed is
nullper bin when the sensor did not report that component; typedprofile_not_foundandno_current_dataerrors
noaa_marine_get_ocean_observations tool
Water temperature, conductivity, salinity, dissolved oxygen (% and ppm), chlorophyll, turbidity, pH, and redox potential per depth
Water-column counterpart to
noaa_marine_get_conditions(surface weather and sea state)Sensor coverage is sparse — most stations report only temperature and salinity; unreported values are
null, never a fabricated zeroFind candidates with
find_stationsusingsource="ndbc"andtypes: ["water_quality"], NDBC's own water-quality catalog flag — a strong hint, not a guarantee, so expectobservations_not_foundon a flagged station serving no.oceanfile
noaa-marine://station/{station_id} resource
Station record as
application/json— name, coordinates, source, capabilities, state, the CO-OPSprediction_class(withreference_idor per-binbins[], exactly as onnoaa_marine_find_stations), and (NDBC) platform classstation_idcomes fromnoaa_marine_find_stationsTyped
station_not_foundwhen both catalogs were read and neither carries the ID, andsource_unavailablewhen a catalog could not be read — a station only the unread catalog carries is never reported as nonexistentCached with a 6-hour TTL (
cacheHint)
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:
In-memory station cache (6-hour TTL) for CO-OPS and NDBC station lists — discovery is fast after first startup
CO-OPS and NDBC integrated in a unified station model —
find_stationsfans out across both sources in parallelNDBC fixed-width text parser normalizes
MM(missing sensor data) tonull, never passes it through as a stringPaired water-level and prediction fetches for storm-surge residual computation
CO-OPS
application=courtesy parameter sent on every request (configurable viaNOAA_APPLICATION_ID)
Agent-friendly output:
Datum echoed on every tide/water-level response so agents state units and reference correctly without assumptions
total_foundonfind_stationsshows the count before thelimitslice, so agents know whether to re-queryAll NDBC sensor fields explicitly nullable — agents don't fabricate missing readings
Typed station
source(coops|ndbc) plus a data-capabilitytypeand (NDBC only) aplatformclass — agents branch on data, not string parsing
Getting started
No API key required. Both NOAA CO-OPS and NDBC are open, keyless data sources.
Public Hosted Instance
Connect directly via Streamable HTTP — no install, no API key:
{
"mcpServers": {
"noaa-marine": {
"type": "streamable-http",
"url": "https://noaa-marine.caseyjhand.com/mcp"
}
}
}Self-Hosted / Local
Add the following to your MCP client configuration file:
{
"mcpServers": {
"noaa-marine": {
"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": {
"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": {
"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/mcpPrerequisites
Bun v1.4.0 or higher (or Node.js v24+).
No external API keys needed — NOAA CO-OPS and NDBC are fully open.
Installation
Clone the repository:
git clone https://github.com/cyanheads/noaa-marine-mcp-server.gitNavigate into the directory:
cd noaa-marine-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env if needed (all vars optional)Configuration
Variable | Description | Default |
| Courtesy identifier sent as |
|
| Transport: |
|
| Port for HTTP server. |
|
| Auth mode: |
|
| HTTP session mode: |
|
| Log level (RFC 5424). |
|
| Directory for log files (Node.js only). |
|
| Enable OpenTelemetry instrumentation. |
|
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:httpRun 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-serverThe 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 |
|
|
|
|
| CO-OPS Tides & Currents API client: station list cache, data fetch, error detection. |
| NDBC buoy service: active stations XML parser, realtime text parser. |
| Seven tool definitions ( |
| Station metadata resource ( |
| Vitest tests mirroring |
| 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 logicUse
ctx.logfor request-scoped loggingRegister 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 testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Tides MCP — NOAA Tides and Currents data
NOAA Tides & Currents — observations, predictions, datums, station metadata
Search NOAA climate stations and datasets, fetch historical weather observations.
Marine MCP — wraps marine-api.open-meteo.com (free, no auth)
Related MCP Servers
- AlicenseBqualityBmaintenanceThis is an MCP (Model Context Protocol) server that provides tools for interacting with the NOAA Tides and Currents API.2426 npm6MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that provides access to NOAA Tides & Currents data including tide predictions, water levels, currents, and meteorological observations.3 npmMIT
- FlicenseNot gradedqualityDmaintenanceMinimal MCP server exposing tools for kitesurfing forecasts (wind and waves via Open-Meteo) and tide predictions (via NOAA CO-OPS).-
- FlicenseNot gradedqualityBmaintenanceMCP server that provides tide predictions, station lookup, and tidal event alerts for US coastal locations using live NOAA data.-