Add Starlight docs site and MCP HTTP transport

- Starlight documentation with 12 Diátaxis-organized pages covering
  getting started, how-to guides, reference, and architecture
- Docker Compose with two services: docs (Starlight/Caddy) and
  mcp (FastMCP streamable-http), both behind caddy-docker-proxy
- Env-based transport selection in server.py — stdio default preserved,
  MCP_TRANSPORT=streamable-http activates HTTP mode for Docker
- Ocean/maritime theme with dark mode default
This commit is contained in:
Ryan Malloy 2026-02-22 19:05:12 -07:00
parent c7320e599b
commit a02d660764
30 changed files with 10478 additions and 1 deletions

5
.env.example Normal file
View File

@ -0,0 +1,5 @@
COMPOSE_PROJECT_NAME=mcnoaa-tides
APP_ENV=prod
DOMAIN=mcnoaa-tides.l.warehack.ing
# Uncomment for dev behind reverse proxy:
# VITE_HMR_HOST=mcnoaa-tides.l.warehack.ing

5
.gitignore vendored
View File

@ -12,3 +12,8 @@ build/
.pytest_cache/
.mypy_cache/
*.so
# Docs site
docs/node_modules/
docs/dist/
docs/.astro/

27
Dockerfile.mcp Normal file
View File

@ -0,0 +1,27 @@
# --- Dependencies ---
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS deps
WORKDIR /app
COPY pyproject.toml uv.lock ./
COPY src/ src/
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-editable
# --- Runtime ---
FROM python:3.12-slim-bookworm AS runtime
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV PATH="/app/.venv/bin:$PATH"
COPY --from=deps /app/.venv /app/.venv
COPY src/ src/
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/mcp')" || exit 1
USER nobody
CMD ["mcnoaa-tides"]

38
Makefile Normal file
View File

@ -0,0 +1,38 @@
.PHONY: dev prod build logs down clean restart shell-docs shell-mcp
.DEFAULT_GOAL := dev
dev:
@echo "Starting mcnoaa-tides in development mode..."
APP_ENV=dev docker compose up -d --build
@echo "Docs: https://$(shell grep DOMAIN .env | head -1 | cut -d= -f2)"
@echo "MCP: https://mcp.$(shell grep DOMAIN .env | head -1 | cut -d= -f2)/mcp"
@sleep 2
docker compose logs -f
prod:
@echo "Starting mcnoaa-tides in production mode..."
APP_ENV=prod docker compose up -d --build
@echo "Docs: https://$(shell grep DOMAIN .env | head -1 | cut -d= -f2)"
@echo "MCP: https://mcp.$(shell grep DOMAIN .env | head -1 | cut -d= -f2)/mcp"
@sleep 3
docker compose logs --tail=20
build:
docker compose build
logs:
docker compose logs -f
down:
docker compose down
clean:
docker compose down -v --rmi local
restart: down dev
shell-docs:
docker compose exec docs sh
shell-mcp:
docker compose exec mcp sh

59
docker-compose.yml Normal file
View File

@ -0,0 +1,59 @@
services:
docs:
build:
context: ./docs
target: ${APP_ENV:-dev}
args:
- DOMAIN=${DOMAIN:-mcnoaa-tides.l.warehack.ing}
restart: unless-stopped
environment:
- DOMAIN=${DOMAIN:-mcnoaa-tides.l.warehack.ing}
- VITE_HMR_HOST=${VITE_HMR_HOST:-}
- ASTRO_TELEMETRY_DISABLED=1
volumes:
# Dev mode: mount source for hot reload
- ./docs/src:/app/src:ro
- ./docs/public:/app/public:ro
- ./docs/astro.config.mjs:/app/astro.config.mjs:ro
networks:
- caddy
labels:
caddy: ${DOMAIN:-mcnoaa-tides.l.warehack.ing}
caddy.reverse_proxy: "{{upstreams 4321}}"
# WebSocket/HMR streaming support
caddy.reverse_proxy.flush_interval: "-1"
caddy.reverse_proxy.transport: "http"
caddy.reverse_proxy.transport.read_timeout: "0"
caddy.reverse_proxy.transport.write_timeout: "0"
caddy.reverse_proxy.transport.keepalive: "5m"
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
caddy.reverse_proxy.stream_timeout: "24h"
caddy.reverse_proxy.stream_close_delay: "5s"
mcp:
build:
context: .
dockerfile: Dockerfile.mcp
restart: unless-stopped
environment:
- MCP_TRANSPORT=streamable-http
- MCP_HOST=0.0.0.0
- MCP_PORT=8000
networks:
- caddy
labels:
caddy: mcp.${DOMAIN:-mcnoaa-tides.l.warehack.ing}
caddy.reverse_proxy: "{{upstreams 8000}}"
# SSE streaming support for MCP server→client messages
caddy.reverse_proxy.flush_interval: "-1"
caddy.reverse_proxy.transport: "http"
caddy.reverse_proxy.transport.read_timeout: "0"
caddy.reverse_proxy.transport.write_timeout: "0"
caddy.reverse_proxy.transport.keepalive: "5m"
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
caddy.reverse_proxy.stream_timeout: "24h"
caddy.reverse_proxy.stream_close_delay: "5s"
networks:
caddy:
external: true

6
docs/.dockerignore Normal file
View File

@ -0,0 +1,6 @@
node_modules
dist
.astro
.env
.git
Makefile

9
docs/Caddyfile.prod Normal file
View File

@ -0,0 +1,9 @@
:4321 {
encode gzip
handle {
root * /usr/share/caddy
try_files {path} {path}/ /index.html
file_server
}
}

26
docs/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
# --- Base ---
FROM node:22-slim AS base
WORKDIR /app
COPY package.json package-lock.json* ./
# --- Dev ---
FROM base AS dev
RUN npm ci
COPY . .
ENV ASTRO_TELEMETRY_DISABLED=1
EXPOSE 4321
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# --- Build ---
FROM base AS build
RUN npm ci
COPY . .
ENV ASTRO_TELEMETRY_DISABLED=1
RUN --mount=type=cache,target=/app/.astro \
npm run build
# --- Prod ---
FROM caddy:2-alpine AS prod
COPY --from=build /app/dist /usr/share/caddy
COPY Caddyfile.prod /etc/caddy/Caddyfile
EXPOSE 4321

61
docs/astro.config.mjs Normal file
View File

@ -0,0 +1,61 @@
import { defineConfig } from "astro/config";
import starlight from "@astrojs/starlight";
const domain = process.env.DOMAIN || "localhost:4321";
const protocol = domain.startsWith("localhost") ? "http" : "https";
export default defineConfig({
site: `${protocol}://${domain}`,
telemetry: false,
devToolbar: { enabled: false },
integrations: [
starlight({
title: "mcnoaa-tides",
description:
"FastMCP server for NOAA CO-OPS tide predictions, water levels, and marine conditions.",
customCss: [
"@fontsource/inter/400.css",
"@fontsource/inter/600.css",
"@fontsource/jetbrains-mono/400.css",
"./src/styles/custom.css",
],
social: [
{
icon: "github",
label: "PyPI",
href: "https://pypi.org/project/mcnoaa-tides/",
},
],
sidebar: [
{
label: "Getting Started",
autogenerate: { directory: "getting-started" },
},
{
label: "How-To Guides",
autogenerate: { directory: "how-to" },
},
{
label: "Reference",
autogenerate: { directory: "reference" },
},
{
label: "Understanding",
autogenerate: { directory: "understanding" },
},
],
}),
],
vite: {
server: {
host: "0.0.0.0",
...(process.env.VITE_HMR_HOST && {
hmr: {
host: process.env.VITE_HMR_HOST,
protocol: "wss",
clientPort: 443,
},
}),
},
},
});

7465
docs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
docs/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "mcnoaa-tides-docs",
"type": "module",
"version": "2026.02.22",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/starlight": "^0.37.6",
"@fontsource/inter": "^5.1.1",
"@fontsource/jetbrains-mono": "^5.1.1",
"@iconify-json/lucide": "^1.2.91",
"astro": "^5.6.1",
"astro-icon": "^1.1.5",
"sharp": "^0.34.2"
}
}

12
docs/public/favicon.svg Normal file
View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<!-- Ocean wave base -->
<path d="M2 20 Q8 14, 16 20 Q24 26, 30 20" stroke="#1a8a8f" stroke-width="2.5" stroke-linecap="round" fill="none"/>
<path d="M2 24 Q8 18, 16 24 Q24 30, 30 24" stroke="#1a8a8f" stroke-width="2" stroke-linecap="round" fill="none" opacity="0.5"/>
<!-- Tide gauge / data indicator -->
<rect x="13" y="5" width="6" height="14" rx="1" fill="#0d3b3e" stroke="#1a8a8f" stroke-width="1"/>
<rect x="14.5" y="8" width="3" height="6" rx="0.5" fill="#5ec4c8"/>
<!-- Signal dots (data points) -->
<circle cx="9" cy="11" r="1.2" fill="#5ec4c8" opacity="0.7"/>
<circle cx="23" cy="11" r="1.2" fill="#5ec4c8" opacity="0.7"/>
<circle cx="16" cy="4" r="1" fill="#5ec4c8"/>
</svg>

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,7 @@
import { defineCollection } from "astro:content";
import { docsLoader } from "@astrojs/starlight/loaders";
import { docsSchema } from "@astrojs/starlight/schema";
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
};

View File

@ -0,0 +1,89 @@
---
title: What is mcnoaa-tides?
description: FastMCP server for NOAA CO-OPS tide predictions, water levels, and marine conditions
sidebar:
order: 1
---
import { Card, CardGrid } from '@astrojs/starlight/components';
**mcnoaa-tides** is an MCP server that connects language models to the
[NOAA CO-OPS Tides and Currents API](https://tidesandcurrents.noaa.gov/api/).
It exposes tide predictions, observed water levels, and meteorological
observations from approximately 301 U.S. coastal stations -- all through a
clean tool interface that any MCP-compatible client can call.
Built for marine planning. Whether you are timing a fishing trip around an
incoming tide, checking wind and pressure before launching a boat, deploying
crab pots at the right tidal phase, or just confirming it is safe to get on
the water -- mcnoaa-tides gives your assistant direct access to the same
federal data mariners and forecasters rely on.
## Capabilities
<CardGrid>
<Card title="13 Tools" icon="rocket">
Station discovery, tide predictions, observed water levels, meteorological
data, marine conditions snapshots, SmartPot deployment intelligence, and
chart visualization.
[Tool reference](/reference/)
</Card>
<Card title="4 Prompt Templates" icon="document">
Guided workflows for fishing trip planning, marine safety checks, SmartPot
deployment assessment, and catch-to-tide correlation analysis.
[Prompt reference](/reference/)
</Card>
<Card title="3 MCP Resources" icon="open-book">
Station catalog (`noaa://stations`), station detail
(`noaa://stations/{id}`), and nearby station lookup
(`noaa://stations/{id}/nearby`).
[Resource reference](/reference/)
</Card>
<Card title="Visualization" icon="seti:image">
Tide charts and multi-panel conditions dashboards in PNG or interactive
HTML. Requires the optional `viz` extras.
[How-to: Charts](/how-to/)
</Card>
</CardGrid>
## Installation
The fastest path is `uvx`, which runs the server without a permanent install:
```bash
# Run directly (no install needed)
uvx mcnoaa-tides
```
To register it as a tool server in Claude Code:
```bash
# Add to Claude Code
claude mcp add mcnoaa-tides -- uvx mcnoaa-tides
```
If you want tide charts and conditions dashboards, install with visualization
support:
```bash
# With visualization support (matplotlib + plotly)
uv pip install mcnoaa-tides[viz]
```
For HTTP transport (useful in Docker or multi-client setups):
```bash
# Streamable HTTP endpoint
MCP_TRANSPORT=streamable-http MCP_PORT=8000 uvx mcnoaa-tides
```
See the [deployment docs](/how-to/) for full Docker and production configuration.
## Next steps
Head to the [Quickstart](/getting-started/quickstart/) to get your first tide
prediction in under two minutes.

View File

@ -0,0 +1,112 @@
---
title: Quickstart
description: Get your first tide prediction in under 2 minutes
sidebar:
order: 2
---
import { Steps } from '@astrojs/starlight/components';
This guide walks through adding **mcnoaa-tides** to Claude Code and making
your first three requests: finding a station, pulling tide predictions, and
checking marine conditions.
### Prerequisites
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
- [uv](https://docs.astral.sh/uv/) installed (for `uvx`)
<Steps>
1. **Add the server**
Register mcnoaa-tides as an MCP tool server. This tells Claude Code to
start it automatically when needed.
```bash
claude mcp add mcnoaa-tides -- uvx mcnoaa-tides
```
The first launch downloads the package and pre-warms the station cache
(takes a few seconds).
2. **Find a station**
Ask Claude to search for stations near a location:
> Search for tide stations in Seattle
Claude calls `search_stations(query="seattle")` and returns matching
stations. Look for **Station 9447130 -- Seattle, WA**.
```json
[
{
"id": "9447130",
"name": "Seattle",
"state": "WA",
"lat": 47.6026,
"lng": -122.3393,
"is_tidal": true
}
]
```
3. **Get tide predictions**
Ask for the tide schedule at that station:
> What are the tide predictions for Seattle station 9447130?
Claude calls `get_tide_predictions(station_id="9447130")` and returns the
next 48 hours of high and low tides:
```json
{
"predictions": [
{ "t": "2026-02-22 03:18", "v": "11.284", "type": "H" },
{ "t": "2026-02-22 10:42", "v": "-1.039", "type": "L" },
{ "t": "2026-02-22 16:54", "v": "10.571", "type": "H" },
{ "t": "2026-02-22 22:36", "v": "3.195", "type": "L" }
]
}
```
`type: "H"` is high tide, `"L"` is low. Values are in feet relative to
MLLW (mean lower low water).
4. **Check conditions**
Get a full marine conditions snapshot in a single call:
> Get a marine conditions snapshot for Seattle station 9447130
Claude calls `marine_conditions_snapshot(station_id="9447130")`, which
fetches tide predictions, observed water levels, wind, temperature, and
pressure in parallel:
```json
{
"station_id": "9447130",
"fetched_utc": "2026-02-22T18:30:00+00:00",
"predictions": { "..." : "high/low tide schedule" },
"water_level": { "..." : "recent observations" },
"wind": { "..." : "speed, direction, gusts" },
"air_temperature": { "..." : "current readings" },
"air_pressure": { "..." : "current readings" },
"water_temperature": { "..." : "current readings" }
}
```
Any products the station does not support appear under an `"unavailable"`
key rather than failing the request.
</Steps>
## Next steps
- **[How-To Guides](/how-to/)** -- Plan a fishing trip, run a safety check,
deploy SmartPot, generate charts
- **[Reference](/reference/)** -- Full tool, prompt, and resource documentation
- **[Understanding](/understanding/)** -- How the tidal engine works, station
coverage, and data freshness

View File

@ -0,0 +1,143 @@
---
title: Plan a Fishing Trip
description: Use tide and weather data to find the best fishing windows
sidebar:
order: 1
---
import { Aside } from '@astrojs/starlight/components';
A productive day on the water starts with knowing what the tide is doing and what the weather has in store. This guide walks through using mcnoaa-tides to find the best fishing windows at any coastal location.
## 1. Find stations near your fishing spot
Start by locating the nearest NOAA tide stations. Pass your approximate GPS coordinates and the tool returns stations sorted by distance in nautical miles.
```json title="Tool call"
{
"tool": "find_nearest_stations",
"arguments": {
"latitude": 47.6062,
"longitude": -122.3321,
"limit": 5
}
}
```
```json title="Sample response"
[
{
"id": "9447130",
"name": "Seattle",
"state": "WA",
"lat": 47.6026,
"lng": -122.3393,
"is_tidal": true,
"distance_nm": 0.3
},
{
"id": "9447110",
"name": "Shilshole Bay",
"state": "WA",
"lat": 47.6648,
"lng": -122.4085,
"is_tidal": true,
"distance_nm": 4.1
}
]
```
<Aside type="tip">
If you are fishing offshore or between two bodies of water, check multiple stations. Tide timing can differ by 30 minutes or more between stations only a few miles apart, especially around headlands and narrow channels.
</Aside>
## 2. Check tide predictions
Use the closest tidal station to pull high/low predictions. The default `hilo` interval gives you the turning points -- the times when the tide changes direction -- which is what matters most for fishing.
```json title="Tool call"
{
"tool": "get_tide_predictions",
"arguments": {
"station_id": "9447130",
"hours": 48,
"interval": "hilo"
}
}
```
```json title="Sample response"
{
"predictions": [
{ "t": "2026-02-22 03:18", "v": "10.21", "type": "H" },
{ "t": "2026-02-22 09:42", "v": "2.14", "type": "L" },
{ "t": "2026-02-22 15:54", "v": "11.87", "type": "H" },
{ "t": "2026-02-22 22:06", "v": "-0.32", "type": "L" }
]
}
```
**Reading the predictions:**
- `type: "H"` = high tide, `type: "L"` = low tide
- `v` = predicted water level in feet above MLLW (Mean Lower Low Water)
- Negative values indicate the water drops below the average low-tide mark
<Aside type="note">
**Why incoming tide matters:** A rising (flood) tide pushes baitfish, shrimp, and other forage into estuaries, marshes, and along structure. Most inshore species feed actively during this movement. The period from two hours before high tide through the first hour of the ebb is often the most productive window.
</Aside>
## 3. Check weather conditions
A good tide window is wasted if the wind is howling. Use `marine_conditions_snapshot` to pull tide predictions, observed water levels, wind, temperature, and pressure in a single call.
```json title="Tool call"
{
"tool": "marine_conditions_snapshot",
"arguments": {
"station_id": "9447130",
"hours": 24
}
}
```
The response includes multiple products fetched in parallel. The fields most relevant for fishing:
| Product | Key fields | What to look for |
|---------|-----------|-----------------|
| `wind` | `s` (speed kn), `g` (gust kn), `dr` (direction) | Sustained under 15 kn for comfortable small-boat fishing |
| `air_pressure` | `v` (millibars) | Falling pressure often correlates with improved bite rates |
| `water_temperature` | `v` (degrees F) | Species-dependent -- cold water slows metabolism and feeding |
| `air_temperature` | `v` (degrees F) | Personal comfort and safety planning |
<Aside type="caution">
Products that are not available at a station appear under the `unavailable` key rather than causing the entire request to fail. Not every NOAA station has wind or temperature sensors -- check `get_station_info` to see what a specific station supports.
</Aside>
## 4. Use the prompt template
For a guided experience, use the `plan_fishing_trip` prompt. It orchestrates all of the above steps and produces a structured recommendation.
```json title="Prompt call"
{
"prompt": "plan_fishing_trip",
"arguments": {
"location": "Seattle, WA",
"target_species": "chinook salmon",
"date": "2026-02-22"
}
}
```
The prompt walks through station discovery, tide analysis, and conditions assessment, then recommends a fishing window that aligns favorable tide phase with manageable weather. It includes the best tide window, expected water levels, a weather summary, and any safety concerns.
Both `target_species` and `date` are optional. If omitted, the prompt defaults to general advice for the current day.
## Tips for better results
**Water temperature drives species behavior.** Most gamefish have a preferred temperature band. Salmon feed aggressively in the 48-56 F range. Striped bass slow down below 50 F. If the water temperature is outside a species' comfort zone, even perfect tide timing may not help.
**Falling barometric pressure is your friend.** Fish sense pressure changes through their swim bladders. A steady drop of 2-4 mb over several hours often triggers feeding activity, particularly in the hour or two before a front arrives. After the front passes and pressure stabilizes, the bite usually slows.
**Tidal range matters as much as timing.** Spring tides (around new and full moons) produce stronger currents and more water movement, which can concentrate bait and trigger aggressive feeding. Neap tides move less water and may produce slower fishing in current-dependent spots.
**Check the datum.** All water level values default to MLLW (Mean Lower Low Water). If you need levels relative to a different reference, pass the `datum` parameter -- for example, `datum: "MSL"` for mean sea level or `datum: "NAVD"` for the North American Vertical Datum.

View File

@ -0,0 +1,241 @@
---
title: SmartPot Deployment
description: Deploy and monitor autonomous crab pots with tidal intelligence
sidebar:
order: 2
---
import { Aside } from '@astrojs/starlight/components';
SmartPot tools bridge NOAA tide data with autonomous crab pot operations. This guide covers the full deployment lifecycle: assessing conditions, deploying at the right tidal phase, monitoring for anomalies during soak, and enriching catch records with tidal context for long-term analysis.
## 1. Check tidal phase at your location
Before heading to the deployment site, determine what the tide is doing right now. The `tidal_phase` tool accepts GPS coordinates and automatically finds the nearest NOAA tidal station.
```json title="Tool call"
{
"tool": "tidal_phase",
"arguments": {
"latitude": 48.3928,
"longitude": -122.6775
}
}
```
```json title="Sample response"
{
"station": {
"id": "9448576",
"name": "Anacortes",
"lat": 48.5117,
"lng": -122.618
},
"station_distance_nm": 7.2,
"timestamp_utc": "2026-02-22T14:30:00+00:00",
"phase": "flood",
"description": "Tide is rising (flood) -- water moving shoreward",
"previous": { "type": "low", "time": "2026-02-22 11:48", "level_ft": -0.42 },
"next": { "type": "high", "time": "2026-02-22 17:54", "level_ft": 9.87 },
"minutes_since_previous": 162,
"minutes_to_next": 204,
"progress_pct": 44.3,
"latest_observed": { "time": "2026-02-22 14:24", "level_ft": 4.61 }
}
```
The response tells you the current phase (ebb, flood, slack_high, or slack_low), how far through the phase you are, and the bracketing high/low events.
<Aside type="tip">
**Deploy during slack or early flood.** Slack water minimizes current forces on the pot during initial settling. Early flood means the first hours of soak coincide with rising water -- when crabs are most active and foraging shoreward.
</Aside>
You can also pass a `station_id` directly if you already know your reference station:
```json
{
"tool": "tidal_phase",
"arguments": {
"station_id": "9448576"
}
}
```
## 2. Get a deployment assessment
The `deployment_briefing` tool evaluates current conditions against safety thresholds and produces a GO, CAUTION, or NO-GO recommendation.
```json title="Tool call"
{
"tool": "deployment_briefing",
"arguments": {
"latitude": 48.3928,
"longitude": -122.6775,
"soak_hours": 48
}
}
```
```json title="Sample response (abbreviated)"
{
"station": { "id": "9448576", "name": "Anacortes" },
"station_distance_nm": 7.2,
"soak_window": {
"begin_utc": "20260222 14:30",
"end_utc": "20260224 14:30",
"hours": 48,
"tidal_cycles": 4
},
"tide_schedule": [
{ "type": "high", "time_utc": "2026-02-22 17:54", "level_ft": 9.87 },
{ "type": "low", "time_utc": "2026-02-23 00:12", "level_ft": 3.21 },
{ "type": "high", "time_utc": "2026-02-23 06:36", "level_ft": 8.44 }
],
"conditions": {
"wind": { "speed_kn": 8.2, "gust_kn": 12.4, "direction": "NW" },
"water_temperature_f": 46.3,
"air_pressure_mb": 1018.4
},
"assessment": "GO",
"advisories": []
}
```
### Assessment criteria
The briefing applies these thresholds automatically:
| Condition | GO | CAUTION | NO-GO |
|-----------|-----|---------|-------|
| Sustained wind | Under 15 kn | 15-20 kn | Over 20 kn |
| Wind gusts | Under 25 kn | 25-30 kn | Over 30 kn |
| Water temperature | Above 40 F | -- | -- (advisory below 40 F) |
| Barometric pressure | Stable or rising | -- | -- (advisory if falling >3 mb) |
<Aside type="caution">
A single NO-GO factor overrides everything else. If wind is 22 kn sustained, the assessment is NO-GO regardless of how favorable the tide or temperature might be.
</Aside>
## 3. Monitor for anomalies during soak
During a multi-day soak, conditions can change. The `water_level_anomaly` tool compares observed water levels against predictions to detect storm surge, seiche events, or unusual deviations that might affect pot stability or recovery.
```json title="Tool call"
{
"tool": "water_level_anomaly",
"arguments": {
"station_id": "9448576",
"window_hours": 6,
"threshold_ft": 0.5
}
}
```
```json title="Sample response"
{
"station_id": "9448576",
"window_hours": 6,
"threshold_ft": 0.5,
"risk_level": "normal",
"explanation": "Water levels within 0.5 ft of predictions -- conditions as expected",
"max_deviation_ft": 0.31,
"mean_deviation_ft": 0.14,
"direction": "above",
"sample_count": 60
}
```
Risk levels are based on the deviation threshold (default 0.5 ft):
| Risk level | Deviation | Meaning |
|-----------|-----------|---------|
| **normal** | Under threshold | Conditions tracking predictions -- no action needed |
| **elevated** | 1x to 2x threshold | Moderate deviation -- monitor conditions more frequently |
| **high** | Over 2x threshold | Possible storm surge or seiche -- consider early recovery |
<Aside type="danger">
A "high" risk level with the `direction` reported as "above" predictions can indicate storm surge pushing water levels well beyond forecast. This increases the risk of pot displacement and makes recovery more hazardous. Factor in the current wind assessment before deciding to deploy a retrieval run.
</Aside>
## 4. Analyze historical catch data
After collecting catch records over multiple deployments, use `catch_tidal_context` to enrich each event with the tidal phase at the time and location of retrieval. This reveals whether certain tidal conditions consistently produce better catches.
```json title="Tool call"
{
"tool": "catch_tidal_context",
"arguments": {
"events": [
{
"timestamp": "2026-02-18T10:30:00Z",
"latitude": 48.3928,
"longitude": -122.6775,
"catch_count": 14,
"species": "dungeness"
},
{
"timestamp": "2026-02-19T16:15:00Z",
"latitude": 48.3895,
"longitude": -122.6810,
"catch_count": 22,
"species": "dungeness"
}
]
}
}
```
Each event is returned with a `tidal` key containing the phase classification, progress percentage, and reference station:
```json title="Enriched event (one of two)"
{
"timestamp": "2026-02-19T16:15:00Z",
"latitude": 48.3895,
"longitude": -122.6810,
"catch_count": 22,
"species": "dungeness",
"tidal": {
"station_id": "9448576",
"station_distance_nm": 7.35,
"phase": "flood",
"description": "Tide is rising (flood) -- water moving shoreward",
"progress_pct": 62.4,
"previous": { "type": "low", "time": "2026-02-19 12:30", "level_ft": 0.81 },
"next": { "type": "high", "time": "2026-02-19 18:48", "level_ft": 10.12 }
}
}
```
All original fields (catch_count, species, pot_id, bait_type, or anything else you include) are passed through unchanged.
<Aside type="note">
The batch limit is 100 events per call. For larger datasets, split into batches. The tool groups events by their nearest station internally to minimize redundant API calls.
</Aside>
Over time, patterns emerge. You might find that mid-flood retrievals consistently outperform ebb retrievals, or that catches peak during spring tides. Use the `crab_pot_analysis` prompt to get a structured summary of these correlations.
## 5. Use the orchestrated workflow
The `smartpot_deployment` prompt ties all of the above into a single guided workflow. It walks through station discovery, tidal phase check, deployment briefing, anomaly detection, and produces a synthesized deployment recommendation.
```json title="Prompt call"
{
"prompt": "smartpot_deployment",
"arguments": {
"latitude": "48.3928",
"longitude": "-122.6775",
"soak_hours": "48"
}
}
```
The prompt produces:
- An overall GO/CAUTION/NO-GO with reasoning
- The optimal deployment window within the next 6 hours
- Expected tidal cycles during the soak period
- Safety or recovery concerns
- A recommended recovery time based on the tide schedule
<Aside type="tip">
Station data accuracy decreases beyond roughly 10 nm from the sensor. The deployment briefing reports the station distance -- if it exceeds 10 nm, treat tide timing as approximate and add a safety margin to your planning.
</Aside>

View File

@ -0,0 +1,121 @@
---
title: Generate Charts
description: Create tide charts and conditions dashboards
sidebar:
order: 3
---
import { Aside } from '@astrojs/starlight/components';
mcnoaa-tides includes two visualization tools that render NOAA data into charts -- a tide prediction plot and a multi-panel conditions dashboard. Both support PNG (inline image) and HTML (interactive Plotly) output formats.
## Prerequisites
The visualization tools require optional dependencies that are not installed by default. Install the `viz` extras:
```bash
uv pip install mcnoaa-tides[viz]
```
This adds `matplotlib` (for PNG rendering) and `plotly` (for interactive HTML charts). If the extras are missing, the tools return a clear error message with the install command.
## Tide chart
The `visualize_tides` tool renders a water level curve with high and low tide markers. It fetches 6-minute interval predictions for a smooth line and overlays observed water levels as a dashed comparison trace.
```json title="Tool call"
{
"tool": "visualize_tides",
"arguments": {
"station_id": "9447130",
"hours": 48,
"include_observed": true,
"format": "png"
}
}
```
### Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `station_id` | required | 7-digit NOAA station ID |
| `hours` | `48` | Time window for the chart |
| `include_observed` | `true` | Overlay actual water level readings as a dashed line |
| `format` | `"png"` | Output format: `png` or `html` |
The chart marks each high tide (**H**) and low tide (**L**) on the prediction curve so you can see turning points at a glance. When `include_observed` is enabled, the dashed observed line shows how actual conditions compare to the forecast -- useful for spotting storm surge or wind-driven deviations.
![Tide chart for Seattle station 9447130 showing predicted water levels over 48 hours with high and low markers](../../../assets/tide-chart-seattle.png)
<Aside type="note">
The observed water level overlay only works for the recent past -- NOAA does not provide "observed" data for future hours. For a 48-hour chart, you will typically see observed data for the first portion and predictions only for the remainder.
</Aside>
## Conditions dashboard
The `visualize_conditions` tool creates a multi-panel dashboard with up to four panels showing different marine data products side by side.
```json title="Tool call"
{
"tool": "visualize_conditions",
"arguments": {
"station_id": "9447130",
"hours": 24,
"format": "png"
}
}
```
### Parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `station_id` | required | 7-digit NOAA station ID |
| `hours` | `24` | Time window for the dashboard |
| `format` | `"png"` | Output format: `png` or `html` |
### Dashboard panels
The dashboard renders up to four panels depending on what the station supports:
1. **Tides** -- Predicted water levels with observed overlay and high/low markers
2. **Wind** -- Speed and gust readings in knots with direction
3. **Temperature** -- Air and water temperature traces
4. **Pressure** -- Barometric pressure with trend indication
![Conditions dashboard for Seattle station 9447130 showing tide, wind, temperature, and pressure panels](../../../assets/conditions-dashboard-seattle.png)
<Aside type="tip">
Products that are unavailable at a particular station are simply omitted from the dashboard rather than showing empty panels. A coastal station without a wind sensor will render a three-panel dashboard. Use `get_station_info` to check which products a station supports before charting.
</Aside>
## Output formats
### PNG
PNG format returns an inline image directly in the MCP response. This is the default and works well for quick visual checks during a conversation -- the chart appears immediately without needing to open a separate file.
### HTML
HTML format saves an interactive Plotly chart to `artifacts/charts/` and returns the file path. The filename includes the station ID, chart type, and UTC timestamp:
```
artifacts/charts/9447130_tides_20260222_143000.html
```
Interactive charts support panning, zooming, hovering for exact values, and toggling individual traces on and off. They are useful when you need to examine a specific time window closely or share the chart with someone who wants to explore the data.
<Aside type="note">
The `artifacts/charts/` directory is created automatically if it does not exist. Charts accumulate over time -- you may want to clean out old files periodically.
</Aside>
## Combining charts with analysis
Charts pair well with the data-fetching tools. A common workflow:
1. Run `marine_conditions_snapshot` to get the raw numbers and identify anything notable
2. Generate a `visualize_conditions` dashboard to see the full picture
3. If the tide panel shows an interesting pattern, generate a focused `visualize_tides` chart with a longer time window
For fishing trip planning, a 48-hour tide chart with the observed overlay gives you both the forecast and a visual check on how well predictions have tracked reality over the past day.

View File

@ -0,0 +1,177 @@
---
title: Response Fields
description: Field reference for NOAA API response data
sidebar:
order: 4
---
import { Tabs, TabItem, Aside } from "@astrojs/starlight/components";
This page documents the fields returned by NOAA CO-OPS API responses. Field names are terse single-character codes inherited from the upstream API. Understanding them is essential for parsing tool responses.
---
## Field Reference
### Common fields
These fields appear across multiple response types.
| Field | Context | Description | Format / Values |
|-------|---------|-------------|-----------------|
| `t` | All responses | Timestamp | Local station time: `YYYY-MM-DD HH:MM` |
| `f` | All observations | Data quality flags | Comma-separated integers (e.g. `"0,0,0,0"`) |
### Tide predictions
Fields returned by `get_tide_predictions`.
| Field | Description | Format / Values |
|-------|-------------|-----------------|
| `t` | Timestamp | `YYYY-MM-DD HH:MM` |
| `v` | Predicted water level | String-encoded float, feet above datum |
| `type` | Tide type (hilo interval only) | `"H"` (high tide), `"L"` (low tide) |
<Aside type="note">
The `type` field is only present when using the `"hilo"` interval. Hourly (`"h"`) and 6-minute (`"6"`) interval responses omit it.
</Aside>
### Observed water levels
Fields returned by `get_observed_water_levels`.
| Field | Description | Format / Values |
|-------|-------------|-----------------|
| `t` | Timestamp | `YYYY-MM-DD HH:MM` |
| `v` | Observed water level | String-encoded float, feet above datum |
| `s` | Sigma (standard deviation) | String-encoded float, feet |
| `f` | Data quality flags | Comma-separated integers |
| `q` | Quality assurance level | `"p"` (preliminary), `"v"` (verified) |
### Wind
Fields returned by `get_meteorological_data` with `product="wind"`.
| Field | Description | Format / Values |
|-------|-------------|-----------------|
| `t` | Timestamp | `YYYY-MM-DD HH:MM` |
| `s` | Wind speed | String-encoded float, knots |
| `d` | Wind direction | String-encoded float, degrees true (0-360) |
| `dr` | Compass direction | String abbreviation: `N`, `NE`, `E`, `SE`, `S`, `SW`, `W`, `NW`, etc. |
| `g` | Gust speed | String-encoded float, knots |
| `f` | Data quality flags | Comma-separated integers |
### Other meteorological products
Fields returned by `get_meteorological_data` for non-wind products (`air_temperature`, `water_temperature`, `air_pressure`, `conductivity`, `visibility`, `humidity`, `salinity`).
| Field | Description | Format / Values |
|-------|-------------|-----------------|
| `t` | Timestamp | `YYYY-MM-DD HH:MM` |
| `v` | Measured value | String-encoded float, units depend on product |
| `f` | Data quality flags | Comma-separated integers |
---
## Value Units by Product
All values are returned as strings and must be parsed to numeric types. The NOAA API is configured with `units=english` by default.
| Product | Field | Unit | Notes |
|---------|-------|------|-------|
| `predictions` | `v` | feet | Height above the specified datum |
| `water_level` | `v` | feet | Height above the specified datum |
| `water_level` | `s` | feet | Standard deviation of 6-minute measurement |
| `air_temperature` | `v` | degrees Fahrenheit | |
| `water_temperature` | `v` | degrees Fahrenheit | |
| `wind` | `s` | knots | Sustained wind speed |
| `wind` | `g` | knots | Peak gust speed |
| `wind` | `d` | degrees true | 0 = north, 90 = east, 180 = south, 270 = west |
| `air_pressure` | `v` | millibars (hPa) | Standard atmospheric pressure is ~1013.25 mb |
| `conductivity` | `v` | mS/cm | Millisiemens per centimeter |
| `visibility` | `v` | nautical miles | |
| `humidity` | `v` | percent | Relative humidity (0-100) |
| `salinity` | `v` | PSU | Practical Salinity Units |
<Aside type="caution">
All numeric values are returned as **strings** from the NOAA API (e.g. `"4.521"`, `"12.50"`). Parse them to `float` before performing arithmetic.
</Aside>
---
## Data Quality Flags
The `f` field contains comma-separated integer flags indicating data quality. A value of `"0,0,0,0"` indicates no quality issues.
Individual flag positions and values vary by product. Common flag values:
| Value | Meaning |
|-------|---------|
| `0` | No quality issue detected |
| `1` | Data value exceeds sigma band |
The exact flag schema is defined by NOAA CO-OPS and may vary between products and stations. For critical applications, treat any non-zero flag as a data quality advisory.
---
## Quality Assurance Levels
The `q` field in water level observations indicates the verification status:
| Value | Level | Description |
|-------|-------|-------------|
| `"p"` | Preliminary | Recent data, not yet reviewed by NOAA quality control |
| `"v"` | Verified | Reviewed and approved by NOAA QC processes |
Preliminary data is typically available within minutes of observation. Verified data may lag by weeks or months. For real-time operational use, preliminary data is appropriate. For historical analysis, prefer verified data when available.
---
## Datum Options
Water level values are relative to a vertical datum -- a reference zero point. The `datum` parameter on tide and water level tools selects which reference is used.
| Datum | Full Name | Description |
|-------|-----------|-------------|
| `MLLW` | Mean Lower Low Water | **Default.** Average of the lower of the two daily low tides. Standard chart datum for U.S. nautical charts |
| `MSL` | Mean Sea Level | Average water level over the 19-year tidal epoch (currently 1983-2001) |
| `NAVD` | North American Vertical Datum (1988) | Fixed geodetic reference tied to the North American land survey network. Does not change with sea level |
| `STND` | Station Datum | The station's internal reference zero. Unique to each station |
| `MHW` | Mean High Water | Average of all high tide levels |
| `MHHW` | Mean Higher High Water | Average of the higher of the two daily high tides |
| `MLW` | Mean Low Water | Average of all low tide levels |
| `MTL` | Mean Tide Level | Midpoint between MHW and MLW |
### Choosing a datum
<Tabs>
<TabItem label="Navigation">
Use **MLLW** (the default). This matches NOAA nautical charts, where charted depths are referenced to MLLW. A prediction of `v = "2.5"` with `datum = "MLLW"` means the water is 2.5 feet above the chart datum -- directly comparable to charted soundings.
</TabItem>
<TabItem label="Flood analysis">
Use **NAVD** for comparing water levels to land elevations (flood maps, infrastructure heights). NAVD is a fixed geodetic reference, while tidal datums shift with sea level changes.
</TabItem>
<TabItem label="Anomaly detection">
Use **MLLW** or **STND** for consistent station-level comparisons. The `water_level_anomaly` tool compares observed vs predicted levels using the same datum, so the choice does not affect the deviation calculation.
</TabItem>
<TabItem label="Cross-station">
Use **NAVD** when comparing water levels between stations. Tidal datums (MLLW, MSL, etc.) are computed independently per station and are not directly comparable across stations. NAVD provides a common reference.
</TabItem>
</Tabs>
---
## Timestamp Conventions
Timestamps follow the pattern `YYYY-MM-DD HH:MM` and are in **local station time** by default (`lst_ldt` -- Local Standard Time / Local Daylight Time). The SmartPot tools (`tidal_phase`, `deployment_briefing`, `catch_tidal_context`, `water_level_anomaly`) request data in GMT for consistent cross-timezone comparison.
When calling tools directly, timestamps in the response match the NOAA API's `time_zone` setting:
| Tool category | Time zone | Format |
|--------------|-----------|--------|
| Tides, Weather, Planning | `lst_ldt` (local) | `2026-02-22 09:42` |
| SmartPot tools | `gmt` (UTC) | `2026-02-22 14:42` |
<Aside type="tip">
If you need UTC timestamps from the tides and weather tools, the underlying NOAA API supports a `time_zone` parameter. The MCP tools currently default to local station time for readability.
</Aside>

View File

@ -0,0 +1,204 @@
---
title: Prompts Reference
description: Guided workflow templates for common marine planning tasks
sidebar:
order: 2
---
import { Badge, Aside, Steps } from "@astrojs/starlight/components";
MCP prompts are guided workflow templates that structure multi-step interactions. The LLM client receives the prompt text and follows the workflow, calling the appropriate tools at each step. All four prompts are registered on the `mcnoaa-tides` server.
<Aside type="tip">
Prompts are not tools -- they do not execute logic or return data directly. They provide structured instructions that guide the LLM through a sequence of tool calls to accomplish a task.
</Aside>
---
## plan_fishing_trip
Guided fishing trip planning using tide and weather data. Walks through station discovery, tide analysis, and conditions assessment to recommend optimal fishing windows.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `location` | `str` | yes | -- | Place name or area description (e.g. `"Narragansett Bay"`) |
| `target_species` | `str` | no | `""` | Target species for species-specific recommendations |
| `date` | `str` | no | `""` | Planned date (defaults to today if empty) |
### Workflow
<Steps>
1. **Station discovery** -- Uses `find_nearest_stations` to locate NOAA tide stations near the specified location and selects the closest station with tide prediction support.
2. **Tide analysis** -- Uses `get_tide_predictions` to find the next high and low tides. Incoming (rising) tide is generally better for most species as it pushes bait and forage into estuaries and along structure.
3. **Conditions check** -- Uses `marine_conditions_snapshot` to assess current conditions:
- Wind: sustained > 15 kn makes small-boat fishing uncomfortable
- Water temperature: affects species activity and feeding patterns
- Pressure trend: falling pressure often improves bite rates
4. **Window recommendation** -- Synthesizes a fishing window that aligns favorable tide phase with manageable weather, including expected water level, weather summary, and safety concerns.
</Steps>
### Example usage
```json
{
"prompt": "plan_fishing_trip",
"arguments": {
"location": "Puget Sound near Seattle",
"target_species": "chinook salmon",
"date": "2026-02-25"
}
}
```
The LLM receives the full workflow template and executes each step, calling `find_nearest_stations`, `get_tide_predictions`, and `marine_conditions_snapshot` in sequence.
---
## marine_safety_check
Go/no-go safety assessment for a marine station. Evaluates current conditions against common safety thresholds for recreational boating and fishing.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `station_id` | `str` | yes | -- | 7-digit NOAA station ID (e.g. `"8454000"`) |
### Assessment criteria
The prompt instructs the LLM to evaluate conditions against these thresholds. If any single factor is NO-GO, the overall assessment should be NO-GO.
**Wind**
| Assessment | Sustained | Gusts |
|------------|-----------|-------|
| GO | < 15 kn | < 20 kn |
| CAUTION | 15-20 kn | 20-25 kn |
| NO-GO | > 20 kn | > 25 kn |
**Visibility**
| Assessment | Range |
|------------|-------|
| GO | > 2 nm |
| CAUTION | 1-2 nm |
| NO-GO | < 1 nm |
**Water temperature (hypothermia risk)**
| Risk Level | Temperature |
|------------|-------------|
| LOW RISK | > 70 F |
| MODERATE | 60-70 F (wear PFD, limit exposure) |
| HIGH RISK | < 60 F (survival suit or dry suit recommended) |
**Pressure trend**
Rapidly falling pressure (> 3 mb over 3 hours) suggests an approaching storm system.
### Workflow
<Steps>
1. **Data fetch** -- Uses `marine_conditions_snapshot` to retrieve all available conditions for the station.
2. **Threshold evaluation** -- Evaluates each condition (wind, visibility, water temperature, pressure trend) against the criteria above.
3. **Assessment** -- Provides a clear GO / CAUTION / NO-GO recommendation with reasoning for each factor.
</Steps>
### Example usage
```json
{
"prompt": "marine_safety_check",
"arguments": {
"station_id": "9447130"
}
}
```
---
## smartpot_deployment
Full SmartPot deployment planning workflow. Orchestrates station discovery, tidal phase analysis, deployment briefing, and anomaly detection into a single deployment recommendation.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `latitude` | `str` | yes | -- | Deployment latitude (passed as string) |
| `longitude` | `str` | yes | -- | Deployment longitude (passed as string) |
| `soak_hours` | `str` | no | `"48"` | Planned soak duration in hours (passed as string) |
<Aside type="note">
Parameters are strings because MCP prompt arguments are always strings. The prompt template passes these values into tool calls where they are parsed to the correct types.
</Aside>
### Workflow
<Steps>
1. **Station discovery** -- Uses `find_nearest_stations` to identify the reference tide station. Notes the distance, since data accuracy decreases beyond ~10 nm from the station.
2. **Tidal phase** -- Uses `tidal_phase` to determine the current phase. Deploying during slack or early flood maximizes initial soak productivity.
3. **Deployment briefing** -- Uses `deployment_briefing` to get the GO/CAUTION/NO-GO assessment, including tide schedule, wind, temperature, and pressure advisories for the soak window.
4. **Anomaly check** -- Uses `water_level_anomaly` on the nearest station to detect storm surge or unusual water level deviations that could affect pot stability or recovery.
5. **Synthesis** -- Produces a deployment recommendation with:
- Overall GO/CAUTION/NO-GO with reasoning
- Optimal deployment window within the next 6 hours
- Expected tidal cycles during soak
- Safety or recovery concerns
- Recommended recovery time based on tide schedule
</Steps>
### Example usage
```json
{
"prompt": "smartpot_deployment",
"arguments": {
"latitude": "47.6",
"longitude": "-122.34",
"soak_hours": "72"
}
}
```
---
## crab_pot_analysis
Guided catch-data analysis with tidal correlation. Helps analyze historical catch events against tidal phases to identify productive patterns.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `events_description` | `str` | no | `""` | Description of catch data context or data source |
### Workflow
<Steps>
1. **Data gathering** -- Collects catch event data. Each event needs at minimum: `timestamp` (UTC), `latitude`, `longitude`, and `catch_count` or `weight`. Additional fields like `species`, `pot_id`, and `bait_type` are preserved.
2. **Tidal enrichment** -- Uses `catch_tidal_context` to classify each event as `ebb`, `flood`, `slack_high`, or `slack_low` with progress through the phase.
3. **Phase aggregation** -- Groups enriched events by tidal phase and computes average catch per phase, best-performing phase, and catch distribution.
4. **Pattern analysis** -- Examines whether certain phases consistently outperform, whether there is a sweet spot in phase progress (e.g., early flood vs late flood), and any correlation with tide range (spring vs neap).
5. **Summary** -- Produces a report with top-performing tidal conditions, recommended deployment timing relative to tide, confidence level based on sample size, and suggestions for future data collection.
</Steps>
### Example usage
```json
{
"prompt": "crab_pot_analysis",
"arguments": {
"events_description": "Three weeks of Dungeness crab catch data from Puget Sound, 6 pots"
}
}
```
The LLM will prompt for the actual event data (or read it from a provided source), then run the analysis workflow.

View File

@ -0,0 +1,231 @@
---
title: Resources Reference
description: MCP resource URIs for station data
sidebar:
order: 3
---
import { Aside } from "@astrojs/starlight/components";
MCP resources provide read-only data accessible via URI. Unlike tools, resources do not accept arbitrary parameters -- they are addressed by URI pattern and return structured data. The `mcnoaa-tides` server registers three resources.
<Aside type="tip">
Resources are useful for populating context before calling tools. For example, read `noaa://stations` to browse the full catalog, then use a station ID with the tools.
</Aside>
---
## noaa://stations
The full NOAA CO-OPS station catalog. Returns a JSON array of all ~301 tide and current stations with basic metadata.
**URI:** `noaa://stations`
**Parameters:** None
**Response fields:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | `str` | 7-digit station ID |
| `name` | `str` | Station name |
| `state` | `str` | Two-letter state abbreviation (may be empty for territories) |
| `lat` | `float` | Latitude in decimal degrees |
| `lng` | `float` | Longitude in decimal degrees |
| `tidal` | `bool` | Whether the station supports tidal predictions |
| `greatlakes` | `bool` | Whether the station is on the Great Lakes |
| `shefcode` | `str` | SHEF (Standard Hydrologic Exchange Format) identifier |
**Example response (truncated):**
```json
[
{
"id": "8410140",
"name": "Eastport",
"state": "ME",
"lat": 44.9033,
"lng": -66.985,
"tidal": true,
"greatlakes": false,
"shefcode": "EPTT1"
},
{
"id": "8413320",
"name": "Bar Harbor",
"state": "ME",
"lat": 44.3922,
"lng": -68.205,
"tidal": true,
"greatlakes": false,
"shefcode": "BRHM3"
},
{
"id": "8454000",
"name": "Providence",
"state": "RI",
"lat": 41.8071,
"lng": -71.4012,
"tidal": true,
"greatlakes": false,
"shefcode": "PRVR1"
}
]
```
The station catalog is cached in memory with a 24-hour TTL. If the cache refresh fails, stale data is served rather than raising an error.
---
## noaa://stations/\{station_id\}
Expanded metadata for a single station. Includes sensor inventory, datum offsets, available products, and station disclaimers.
**URI pattern:** `noaa://stations/{station_id}`
**Parameters:**
| Segment | Type | Description |
|---------|------|-------------|
| `station_id` | `str` | 7-digit NOAA station ID |
**Example URI:** `noaa://stations/8454000`
**Example response (abbreviated):**
```json
{
"id": "8454000",
"name": "Providence",
"state": "RI",
"lat": 41.8071,
"lng": -71.4012,
"timezone": "EST",
"timezonecorr": -5,
"observedst": true,
"stormsurge": false,
"nearby": {
"self": "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations/8454000.json"
},
"details": {
"datumoffset": -3.42,
"ctrlStation": "8449130",
"shefcode": "PRVR1",
"accepted": 1938
},
"sensors": [
{"name": "Water Level", "status": "active"},
{"name": "Air Temperature", "status": "active"},
{"name": "Wind", "status": "active"},
{"name": "Barometric Pressure", "status": "active"}
],
"datums": [
{"name": "MHHW", "value": 5.09},
{"name": "MHW", "value": 4.63},
{"name": "MSL", "value": 2.235},
{"name": "MLW", "value": 0.37},
{"name": "MLLW", "value": 0.0},
{"name": "NAVD", "value": 2.159},
{"name": "STND", "value": 6.455}
],
"products": {
"predictions": true,
"water_level": true,
"air_temperature": true,
"water_temperature": false,
"wind": true,
"air_pressure": true,
"conductivity": false,
"visibility": false,
"humidity": false,
"salinity": false
},
"disclaimers": {
"self": "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations/8454000/disclaimers.json"
}
}
```
<Aside type="note">
The metadata API wraps the station in a `"stations"` array. The resource handler extracts the first element automatically, so you receive a single station object.
</Aside>
This resource calls the NOAA metadata API with the `expand=details,sensors,datums,products,disclaimers` parameter to retrieve all available metadata in one request. A 404 response from NOAA raises a `ValueError` indicating the station was not found.
---
## noaa://stations/\{station_id\}/nearby
Stations within 50 nautical miles of a given station, sorted by distance. The queried station itself is excluded from the results.
**URI pattern:** `noaa://stations/{station_id}/nearby`
**Parameters:**
| Segment | Type | Description |
|---------|------|-------------|
| `station_id` | `str` | 7-digit NOAA station ID to search around |
**Example URI:** `noaa://stations/8454000/nearby`
**Response fields:** Same as `noaa://stations` with an additional `distance_nm` field.
| Field | Type | Description |
|-------|------|-------------|
| `id` | `str` | 7-digit station ID |
| `name` | `str` | Station name |
| `state` | `str` | Two-letter state abbreviation |
| `lat` | `float` | Latitude |
| `lng` | `float` | Longitude |
| `tidal` | `bool` | Tidal prediction support |
| `greatlakes` | `bool` | Great Lakes station |
| `shefcode` | `str` | SHEF identifier |
| `distance_nm` | `float` | Distance in nautical miles from the queried station |
**Example response:**
```json
[
{
"id": "8452660",
"name": "Newport",
"state": "RI",
"lat": 41.5043,
"lng": -71.3261,
"tidal": true,
"greatlakes": false,
"shefcode": "NWPR1",
"distance_nm": 19.2
},
{
"id": "8447930",
"name": "Woods Hole",
"state": "MA",
"lat": 41.5236,
"lng": -70.6714,
"tidal": true,
"greatlakes": false,
"shefcode": "WHDM3",
"distance_nm": 38.7
},
{
"id": "8449130",
"name": "Nantucket Island",
"state": "MA",
"lat": 41.285,
"lng": -70.0967,
"tidal": true,
"greatlakes": false,
"shefcode": "NTKM3",
"distance_nm": 48.1
}
]
```
Returns up to 10 nearby stations. Uses the haversine formula to compute distances in nautical miles from the station's coordinates. If the queried station ID is not found in the catalog, an error object is returned:
```json
{
"error": "Station 9999999 not found"
}
```

View File

@ -0,0 +1,773 @@
---
title: Tools Reference
description: Complete reference for all 13 MCP tools
sidebar:
order: 1
---
import { Tabs, TabItem, Badge, Aside } from "@astrojs/starlight/components";
All tools are registered on the `mcnoaa-tides` MCP server and available through any MCP-compatible client. Parameters with defaults are optional.
<Aside type="tip">
Date parameters accept `yyyyMMdd` or `yyyyMMdd HH:mm` format. The maximum time range is 720 hours (~30 days).
</Aside>
---
## Station Discovery
Tools for locating NOAA CO-OPS tide stations by name, geography, or ID.
### search_stations <Badge text="discovery" variant="note" />
Search the station catalog (~301 stations) by name, state, or tidal flag. Returns up to 50 matches.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | `str` | `""` | Case-insensitive substring match against station name or ID |
| `state` | `str` | `""` | Two-letter state abbreviation (e.g. `"RI"`, `"WA"`) |
| `is_tidal` | `bool \| None` | `None` | Filter by tidal capability. `None` returns all stations |
**Example call:**
```json
{
"tool": "search_stations",
"arguments": {
"state": "WA",
"is_tidal": true
}
}
```
**Example response:**
```json
[
{
"id": "9447130",
"name": "Seattle",
"state": "WA",
"lat": 47.6026,
"lng": -122.3393,
"tidal": true,
"greatlakes": false,
"shefcode": "SEWW1"
},
{
"id": "9449424",
"name": "Cherry Point",
"state": "WA",
"lat": 48.8633,
"lng": -122.7583,
"tidal": true,
"greatlakes": false,
"shefcode": "CHYW1"
}
]
```
---
### find_nearest_stations <Badge text="discovery" variant="note" />
Find the closest NOAA stations to a GPS coordinate. Distances are in nautical miles.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `latitude` | `float` | *required* | Decimal degrees latitude |
| `longitude` | `float` | *required* | Decimal degrees longitude |
| `limit` | `int` | `5` | Maximum number of stations to return (minimum 1) |
| `max_distance_nm` | `float` | `100` | Search radius in nautical miles (must be positive) |
**Example call:**
```json
{
"tool": "find_nearest_stations",
"arguments": {
"latitude": 41.49,
"longitude": -71.32,
"limit": 3
}
}
```
**Example response:**
```json
[
{
"id": "8452660",
"name": "Newport",
"state": "RI",
"lat": 41.5043,
"lng": -71.3261,
"tidal": true,
"greatlakes": false,
"shefcode": "NWPR1",
"distance_nm": 0.9
},
{
"id": "8454000",
"name": "Providence",
"state": "RI",
"lat": 41.8071,
"lng": -71.4012,
"tidal": true,
"greatlakes": false,
"shefcode": "PRVR1",
"distance_nm": 19.4
}
]
```
---
### get_station_info <Badge text="discovery" variant="note" />
Retrieve expanded metadata for a single station, including available sensors, datum offsets, supported products, and disclaimers.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID (e.g. `"8454000"`) |
**Example call:**
```json
{
"tool": "get_station_info",
"arguments": {
"station_id": "8454000"
}
}
```
**Example response (abbreviated):**
```json
{
"id": "8454000",
"name": "Providence",
"state": "RI",
"lat": 41.8071,
"lng": -71.4012,
"sensors": [
{"name": "Water Level", "status": "active"},
{"name": "Air Temperature", "status": "active"},
{"name": "Wind", "status": "active"},
{"name": "Barometric Pressure", "status": "active"}
],
"datums": [
{"name": "MLLW", "value": 0.0},
{"name": "MSL", "value": 2.235},
{"name": "NAVD", "value": 2.159}
],
"products": {
"predictions": true,
"water_level": true,
"air_temperature": true,
"wind": true,
"air_pressure": true,
"water_temperature": false
}
}
```
<Aside type="caution">
Not all stations support all products. Always call `get_station_info` first to verify which sensors and products are available before requesting observations.
</Aside>
---
## Tides
Tools for tide predictions and observed water levels.
### get_tide_predictions <Badge text="tides" variant="success" />
Retrieve tide predictions for a station. Defaults to high/low (hilo) predictions for the next 48 hours -- the most useful interval for trip planning.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `begin_date` | `str` | `""` | Start date (`yyyyMMdd` or `yyyyMMdd HH:mm`) |
| `end_date` | `str` | `""` | End date |
| `hours` | `int` | `48` | Duration from now (ignored if both dates are set) |
| `interval` | `str` | `"hilo"` | Prediction interval: `"hilo"`, `"h"` (hourly), or `"6"` (6-minute) |
| `datum` | `str` | `"MLLW"` | Vertical datum reference. See [Datum Options](#datum-options) |
**Example call:**
```json
{
"tool": "get_tide_predictions",
"arguments": {
"station_id": "8454000",
"hours": 24,
"interval": "hilo"
}
}
```
**Example response:**
```json
{
"predictions": [
{"t": "2026-02-22 03:18", "v": "0.287", "type": "L"},
{"t": "2026-02-22 09:42", "v": "4.521", "type": "H"},
{"t": "2026-02-22 15:54", "v": "0.143", "type": "L"},
{"t": "2026-02-22 22:06", "v": "4.893", "type": "H"}
]
}
```
The `type` field is only present with the `"hilo"` interval. `"H"` indicates high tide, `"L"` indicates low tide.
---
### get_observed_water_levels <Badge text="tides" variant="success" />
Retrieve actual observed water level readings from a station. Returns 6-minute interval observations by default.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `begin_date` | `str` | `""` | Start date |
| `end_date` | `str` | `""` | End date |
| `hours` | `int` | `24` | Duration from now |
| `datum` | `str` | `"MLLW"` | Vertical datum reference |
**Example call:**
```json
{
"tool": "get_observed_water_levels",
"arguments": {
"station_id": "8454000",
"hours": 6
}
}
```
**Example response (truncated):**
```json
{
"data": [
{"t": "2026-02-22 12:00", "v": "2.145", "s": "0.003", "f": "0,0,0,0", "q": "p"},
{"t": "2026-02-22 12:06", "v": "2.098", "s": "0.004", "f": "0,0,0,0", "q": "p"},
{"t": "2026-02-22 12:12", "v": "2.051", "s": "0.003", "f": "0,0,0,0", "q": "p"}
]
}
```
Compare observed levels against predictions to see how actual conditions differ from forecast. The `q` field indicates data quality: `"p"` for preliminary, `"v"` for verified.
---
## Weather
Meteorological observations from NOAA station sensors.
### get_meteorological_data <Badge text="weather" variant="caution" />
Retrieve a single meteorological product from a station. Not all stations support all products -- use `get_station_info` to check available sensors first.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `product` | `Literal` | *required* | One of the products listed below |
| `begin_date` | `str` | `""` | Start date |
| `end_date` | `str` | `""` | End date |
| `hours` | `int` | `24` | Duration from now |
**Available products:**
| Product | Response fields | Units |
|---------|----------------|-------|
| `air_temperature` | `t`, `v`, `f` | degrees Fahrenheit |
| `water_temperature` | `t`, `v`, `f` | degrees Fahrenheit |
| `wind` | `t`, `s`, `d`, `dr`, `g`, `f` | speed/gust in knots, direction in degrees |
| `air_pressure` | `t`, `v`, `f` | millibars |
| `conductivity` | `t`, `v`, `f` | mS/cm |
| `visibility` | `t`, `v`, `f` | nautical miles |
| `humidity` | `t`, `v`, `f` | percent |
| `salinity` | `t`, `v`, `f` | PSU |
<Tabs>
<TabItem label="Temperature">
```json
{
"tool": "get_meteorological_data",
"arguments": {
"station_id": "8454000",
"product": "air_temperature",
"hours": 6
}
}
```
```json
{
"data": [
{"t": "2026-02-22 12:00", "v": "38.5", "f": "0,0,0"},
{"t": "2026-02-22 12:06", "v": "38.7", "f": "0,0,0"}
]
}
```
</TabItem>
<TabItem label="Wind">
```json
{
"tool": "get_meteorological_data",
"arguments": {
"station_id": "8454000",
"product": "wind",
"hours": 6
}
}
```
```json
{
"data": [
{
"t": "2026-02-22 12:00",
"s": "8.75",
"d": "225.00",
"dr": "SW",
"g": "12.40",
"f": "0,0"
}
]
}
```
</TabItem>
</Tabs>
---
## Planning
Composite tools that aggregate multiple data sources for marine decision-making.
### marine_conditions_snapshot <Badge text="planning" variant="danger" />
Fetch tide predictions, observed water levels, and meteorological data in a single call. Fires 6 parallel API requests and gracefully handles products that are unavailable at a station.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `hours` | `int` | `24` | Duration for all data products |
**Products fetched:** `predictions` (hilo), `water_level`, `water_temperature`, `air_temperature`, `wind`, `air_pressure`
**Example call:**
```json
{
"tool": "marine_conditions_snapshot",
"arguments": {
"station_id": "8454000"
}
}
```
**Example response (abbreviated):**
```json
{
"station_id": "8454000",
"fetched_utc": "2026-02-22T14:30:00.000000+00:00",
"predictions": {
"predictions": [
{"t": "2026-02-22 09:42", "v": "4.521", "type": "H"},
{"t": "2026-02-22 15:54", "v": "0.143", "type": "L"}
]
},
"water_level": {
"data": [
{"t": "2026-02-22 14:24", "v": "1.203", "s": "0.003", "f": "0,0,0,0", "q": "p"}
]
},
"wind": {
"data": [
{"t": "2026-02-22 14:24", "s": "12.50", "d": "180.00", "dr": "S", "g": "18.20", "f": "0,0"}
]
},
"unavailable": {
"water_temperature": "ValueError: No data for station '8454000' product 'water_temperature'."
}
}
```
Products that fail or lack sensor support appear under the `"unavailable"` key rather than causing the entire request to fail.
---
## SmartPot
Tidal intelligence tools designed for autonomous crab pot deployments. These bridge NOAA tide data with field operations: phase awareness, deployment assessment, catch enrichment, and anomaly detection.
### tidal_phase <Badge text="smartpot" />
Classify the current tidal phase at a station or GPS location. Returns one of four phases: `ebb`, `flood`, `slack_high`, or `slack_low`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | `""` | 7-digit NOAA station ID |
| `latitude` | `float \| None` | `None` | Decimal degrees latitude |
| `longitude` | `float \| None` | `None` | Decimal degrees longitude |
<Aside type="tip">
Provide either `station_id` OR both `latitude` and `longitude`. GPS coordinates automatically resolve to the nearest tidal station.
</Aside>
**Example call:**
```json
{
"tool": "tidal_phase",
"arguments": {
"latitude": 47.6,
"longitude": -122.34
}
}
```
**Example response:**
```json
{
"station": {
"id": "9447130",
"name": "Seattle",
"lat": 47.6026,
"lng": -122.3393
},
"station_distance_nm": 0.2,
"timestamp_utc": "2026-02-22T14:30:00+00:00",
"phase": "flood",
"description": "Tide is rising (flood) -- water moving shoreward",
"previous": {
"type": "low",
"time": "2026-02-22 08:15",
"level_ft": -1.23
},
"next": {
"type": "high",
"time": "2026-02-22 14:48",
"level_ft": 10.87
},
"minutes_since_previous": 375,
"minutes_to_next": 18,
"progress_pct": 95.4,
"latest_observed": {
"time": "2026-02-22 14:24",
"level_ft": 10.52
}
}
```
Slack window is defined as 30 minutes around a high or low event. The `progress_pct` field indicates how far through the current phase the tide has progressed (0 = just started, 100 = reaching next event).
---
### deployment_briefing <Badge text="smartpot" />
GO/CAUTION/NO-GO assessment for crab pot deployment. Finds the nearest station, fetches tide predictions covering the soak window, and evaluates current conditions.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `latitude` | `float` | *required* | Deployment latitude |
| `longitude` | `float` | *required* | Deployment longitude |
| `soak_hours` | `int` | `48` | Planned soak duration in hours |
**Assessment thresholds:**
| Condition | GO | CAUTION | NO-GO |
|-----------|-----|---------|-------|
| Wind sustained | < 15 kn | 15-20 kn | > 20 kn |
| Wind gust | < 25 kn | 25-30 kn | > 30 kn |
| Water temp | -- | -- | < 40 F (cold-water advisory) |
| Pressure trend | stable | -- | dropping > 3 mb/3hr (weather advisory) |
**Example call:**
```json
{
"tool": "deployment_briefing",
"arguments": {
"latitude": 47.6,
"longitude": -122.34,
"soak_hours": 72
}
}
```
**Example response (abbreviated):**
```json
{
"station": {
"id": "9447130",
"name": "Seattle",
"lat": 47.6026,
"lng": -122.3393
},
"station_distance_nm": 0.2,
"timestamp_utc": "2026-02-22T14:30:00+00:00",
"soak_window": {
"begin_utc": "20260222 14:30",
"end_utc": "20260225 14:30",
"hours": 72,
"tidal_cycles": 6
},
"tide_schedule": [
{"type": "high", "time_utc": "2026-02-22 14:48", "level_ft": 10.87},
{"type": "low", "time_utc": "2026-02-22 21:12", "level_ft": 3.45}
],
"conditions": {
"wind": {
"speed_kn": 12.5,
"gust_kn": 18.2,
"direction": "S"
},
"water_temperature_f": 46.3,
"air_pressure_mb": 1013.2
},
"assessment": "GO",
"advisories": []
}
```
---
### catch_tidal_context <Badge text="smartpot" />
Batch-enrich catch events with tidal phase data. Groups events by nearest station and fetches hilo predictions once per station for the entire time window.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `events` | `list[dict]` | *required* | Array of catch event objects (max 100) |
**Required fields per event:**
| Field | Type | Description |
|-------|------|-------------|
| `timestamp` | `str` | ISO-8601 or `"YYYY-MM-DD HH:MM"` (UTC) |
| `latitude` | `float` | GPS latitude of the pot |
| `longitude` | `float` | GPS longitude of the pot |
Extra fields (`catch_count`, `species`, `weight`, `pot_id`, `bait_type`, etc.) are passed through unchanged.
**Example call:**
```json
{
"tool": "catch_tidal_context",
"arguments": {
"events": [
{
"timestamp": "2026-02-20T08:30:00Z",
"latitude": 47.6,
"longitude": -122.34,
"catch_count": 12,
"species": "dungeness"
},
{
"timestamp": "2026-02-20T14:15:00Z",
"latitude": 47.6,
"longitude": -122.34,
"catch_count": 7,
"species": "dungeness"
}
]
}
}
```
**Example response:**
```json
[
{
"timestamp": "2026-02-20T08:30:00Z",
"latitude": 47.6,
"longitude": -122.34,
"catch_count": 12,
"species": "dungeness",
"tidal": {
"station_id": "9447130",
"station_distance_nm": 0.2,
"phase": "flood",
"description": "Tide is rising (flood) -- water moving shoreward",
"previous": {"type": "low", "time": "2026-02-20 06:45", "level_ft": -0.82},
"next": {"type": "high", "time": "2026-02-20 13:12", "level_ft": 11.03},
"minutes_since_previous": 105,
"minutes_to_next": 282,
"progress_pct": 27.1
}
},
{
"timestamp": "2026-02-20T14:15:00Z",
"latitude": 47.6,
"longitude": -122.34,
"catch_count": 7,
"species": "dungeness",
"tidal": {
"station_id": "9447130",
"station_distance_nm": 0.2,
"phase": "ebb",
"description": "Tide is falling (ebb) -- water moving seaward",
"previous": {"type": "high", "time": "2026-02-20 13:12", "level_ft": 11.03},
"next": {"type": "low", "time": "2026-02-20 19:30", "level_ft": 2.14},
"minutes_since_previous": 63,
"minutes_to_next": 315,
"progress_pct": 16.7
}
}
]
```
---
### water_level_anomaly <Badge text="smartpot" />
Compare observed water levels against predictions to detect storm surge, seiche events, or sensor drift.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `window_hours` | `int` | `6` | Lookback window for comparison |
| `threshold_ft` | `float` | `0.5` | Deviation threshold in feet |
**Risk levels:**
| Level | Condition | Meaning |
|-------|-----------|---------|
| `normal` | max deviation < threshold | Conditions as expected |
| `elevated` | threshold &lt;= deviation &lt; 2x threshold | Moderate deviation, monitor conditions |
| `high` | deviation &gt;= 2x threshold | Possible storm surge, seiche, or weather influence |
**Example call:**
```json
{
"tool": "water_level_anomaly",
"arguments": {
"station_id": "8454000",
"window_hours": 12,
"threshold_ft": 0.3
}
}
```
**Example response:**
```json
{
"station_id": "8454000",
"window_hours": 12,
"threshold_ft": 0.3,
"risk_level": "elevated",
"explanation": "Water levels are 0.42 ft above predictions -- moderate deviation, monitor conditions",
"max_deviation_ft": 0.42,
"mean_deviation_ft": 0.18,
"direction": "above",
"sample_count": 120
}
```
---
## Visualization
Chart generation tools. Require the `mcnoaa-tides[viz]` optional dependency (`matplotlib` for PNG, `plotly` for HTML).
### visualize_tides <Badge text="visualization" variant="note" />
Generate a tide prediction chart with high/low markers and optional observed water level overlay.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `hours` | `int` | `48` | Time window for the chart |
| `include_observed` | `bool` | `True` | Overlay observed water levels as a dashed line |
| `format` | `Literal["png", "html"]` | `"png"` | Output format |
**Return value:**
- `"png"` -- Returns an inline `Image` object (rendered directly in MCP clients that support images)
- `"html"` -- Saves an interactive Plotly chart to `artifacts/charts/` and returns the file path
**Example call:**
```json
{
"tool": "visualize_tides",
"arguments": {
"station_id": "9447130",
"hours": 72,
"format": "html"
}
}
```
**Example response (html format):**
```
Interactive tide chart saved to: artifacts/charts/9447130_tides_20260222_143000.html
```
---
### visualize_conditions <Badge text="visualization" variant="note" />
Generate a multi-panel marine conditions dashboard with up to 4 panels: tides with observed overlay, wind speed and gust, air and water temperature, and barometric pressure with trend.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `station_id` | `str` | *required* | 7-digit NOAA station ID |
| `hours` | `int` | `24` | Time window for all panels |
| `format` | `Literal["png", "html"]` | `"png"` | Output format |
Products unavailable at a station are omitted from the dashboard rather than causing an error.
**Example call:**
```json
{
"tool": "visualize_conditions",
"arguments": {
"station_id": "9447130",
"hours": 48,
"format": "png"
}
}
```
**Return value:** Same behavior as `visualize_tides` -- inline image for PNG, file path for HTML.
<Aside type="note">
Install visualization dependencies with `uv pip install mcnoaa-tides[viz]`. The server runs without them but these two tools will raise a `ValueError` with an install hint if the dependencies are missing.
</Aside>
---
## Datum Options
Several tools accept a `datum` parameter. The most common options:
| Datum | Full Name | Description |
|-------|-----------|-------------|
| `MLLW` | Mean Lower Low Water | Default. The average of the lower of the two daily low tides |
| `MSL` | Mean Sea Level | Average water level over the tidal epoch |
| `NAVD` | North American Vertical Datum (1988) | Fixed geodetic reference |
| `STND` | Station Datum | The station's internal reference zero |
| `MHW` | Mean High Water | Average of all high tide levels |
| `MHHW` | Mean Higher High Water | Average of the higher of the two daily high tides |
| `MLW` | Mean Low Water | Average of all low tide levels |
| `MTL` | Mean Tide Level | Midpoint between MHW and MLW |

View File

@ -0,0 +1,165 @@
---
title: "Architecture"
description: "How mcnoaa-tides is built -- server lifecycle, caching, and parallel fetch patterns"
sidebar:
order: 1
---
import { Aside, Card, CardGrid, Steps, FileTree, Badge } from "@astrojs/starlight/components";
mcnoaa-tides is a FastMCP server that wraps the NOAA CO-OPS Tides and Currents API. This page describes how the pieces fit together -- the server lifecycle, caching strategy, parallel fetch patterns, and module organization.
## Server Lifecycle
The server uses FastMCP's lifespan context manager to own the `NOAAClient` instance. Every tool call receives the same client through the lifespan context, so there is exactly one HTTP connection pool and one station cache for the entire server process.
<Steps>
1. **Startup** -- The lifespan manager creates a `NOAAClient` and calls `initialize()`, which opens an `httpx.AsyncClient` (with a 30-second timeout) and pre-warms the station cache by fetching the full station catalog from the NOAA metadata API.
2. **Pre-warm fallback** -- If the station cache request fails during startup (network down, NOAA API outage), the server does not crash. It creates a bare HTTP client and logs a warning to stderr. The cache will populate on the first station-related request.
3. **Running** -- The initialized client is yielded into the FastMCP context as `{"noaa_client": client}`. Every tool accesses it via `ctx.lifespan_context["noaa_client"]`.
4. **Shutdown** -- The `finally` block calls `client.close()`, which properly closes the `httpx.AsyncClient` and releases its connection pool.
</Steps>
```python
# Simplified view of the lifespan flow
@asynccontextmanager
async def lifespan(server: FastMCP):
client = NOAAClient()
try:
await client.initialize() # HTTP client + station cache
except Exception:
client._http = httpx.AsyncClient(timeout=30) # fallback
try:
yield {"noaa_client": client}
finally:
await client.close()
```
## Station Cache
The station catalog contains roughly 301 entries. Fetching it on every request would be wasteful, so `NOAAClient` caches it in memory with a 24-hour TTL.
| Property | Value |
|---|---|
| TTL | 86,400 seconds (24 hours) |
| Storage | In-memory `list[Station]` on the client instance |
| Refresh trigger | Any call to `get_stations()` after TTL expires |
| Failure behavior | Serve stale data if cache was previously populated; raise if cache was never populated |
The refresh strategy is **stale-while-revalidate**: when the TTL expires, `get_stations()` attempts a background refresh. If the refresh fails but stale data exists, the stale list is returned and a warning is logged to stderr. This means the station list might be up to 48 hours old in the worst case (24h TTL + 24h failed refresh window), but requests never fail just because the metadata API is temporarily unreachable.
<Aside type="note">
The cache is not persisted to disk. Restarting the server always triggers a fresh fetch. For typical usage through Claude Code or similar MCP clients, a single server process stays alive for the duration of a session.
</Aside>
## Two NOAA APIs
mcnoaa-tides talks to two distinct NOAA endpoints. Neither requires an API key.
<CardGrid>
<Card title="Data API">
**Base URL:** `api.tidesandcurrents.noaa.gov/api/prod/datagetter`
Returns observations and predictions -- water levels, tide predictions, wind, air temperature, water temperature, air pressure, and more. Accepts station ID, product name, date range, datum, and units.
</Card>
<Card title="Metadata API">
**Base URL:** `api.tidesandcurrents.noaa.gov/mdapi/prod/webapi`
Returns station information -- the station catalog (`stations.json`), individual station details, available sensors, datums, and products. Used for station discovery and the station cache.
</Card>
</CardGrid>
Station IDs are 7-digit numbers (e.g., `9447130` for Seattle, `8454000` for Providence). The client validates this format with a regex before making any request.
Every data API request includes the query parameter `application=mcnoaa-tides-mcp` so NOAA can identify the source if needed.
## Parallel Fetching
Several tools fire multiple API calls simultaneously using `asyncio.gather`. This is the primary performance optimization -- a snapshot that needs 6 products takes about as long as a single product request rather than 6 times as long.
### marine_conditions_snapshot
Fetches 6 products in parallel:
```
predictions (hilo) | water_level | water_temperature
air_temperature | wind | air_pressure
```
Each product is fetched independently. If a product fails (sensor not available at the station, temporary API error), it is recorded under an `"unavailable"` key in the response rather than failing the entire request.
### deployment_briefing
Fetches 4 products in parallel:
```
predictions (hilo) | wind | water_temperature | air_pressure
```
Uses a `_safe_fetch` wrapper that returns `None` on failure for the meteorological products. The hilo prediction fetch is not wrapped -- if tides cannot be retrieved, the tool raises rather than returning a deployment briefing without tide data.
### visualize_conditions
Fetches 6 products in parallel (same set as `marine_conditions_snapshot`), then passes the collected data to the chart renderer. Unavailable products are omitted from the dashboard panels.
### Error isolation
The pattern across all parallel-fetch tools is the same: wrap each product request in a try/except, collect successes and failures separately, and report failures without crashing the overall request. This is important because not every station supports every product -- a station might have a tide gauge but no wind sensor.
## Tool Module Organization
The codebase is split into focused modules, each with a `register(mcp)` function that attaches tools to the FastMCP server instance.
<FileTree>
- src/mcnoaa_tides/
- server.py -- FastMCP app, lifespan, registers all modules
- client.py -- NOAAClient (HTTP, caching, search, haversine)
- models.py -- Pydantic models (Station, TidePrediction, etc.)
- tidal.py -- Pure tidal phase classification (no I/O)
- resources.py -- MCP resources (station catalog, detail, nearby)
- prompts.py -- MCP prompt templates (fishing trip, safety check, etc.)
- tools/
- stations.py -- search_stations, find_nearest_stations, get_station_info
- tides.py -- get_tide_predictions, get_observed_water_levels
- meteorological.py -- get_meteorological_data (8 product types)
- conditions.py -- marine_conditions_snapshot
- smartpot.py -- tidal_phase, deployment_briefing, catch_tidal_context, water_level_anomaly
- charts.py -- visualize_tides, visualize_conditions
- charts/
- tides.py -- Matplotlib/Plotly tide chart renderers
- conditions.py -- Matplotlib/Plotly conditions dashboard renderers
</FileTree>
Each tool module has a single `register()` function. The server imports all six and calls them in sequence during startup. This keeps the server module small and makes it straightforward to add new tool groups without touching existing ones.
### Pure logic separation
`tidal.py` contains the phase classification algorithm and prediction interpolation functions. It has no I/O, no FastMCP dependencies, and no async code -- just datetime math. This makes it independently testable and reusable. The SmartPot tools import from `tidal.py` to classify phases and detect anomalies.
### MCP resources and prompts
Beyond tools, the server registers three **resources** (station catalog, station detail, nearby stations) accessible via `noaa://` URIs, and four **prompt templates** (fishing trip planning, marine safety check, SmartPot deployment, and catch analysis) that guide LLMs through multi-step workflows.
## Transport Modes
The server supports two transport modes, selected by the `MCP_TRANSPORT` environment variable:
| Transport | When to use | How it runs |
|---|---|---|
| `stdio` (default) | Local usage via `uvx mcnoaa-tides` or Claude Code | Reads JSON-RPC from stdin, writes to stdout |
| `streamable-http` | Docker deployment, remote access | Starts an HTTP server on `MCP_HOST`:`MCP_PORT` (defaults to `0.0.0.0:8000`) |
```bash
# stdio (default -- just run it)
uvx mcnoaa-tides
# streamable-http (Docker or remote)
MCP_TRANSPORT=streamable-http MCP_PORT=8000 uvx mcnoaa-tides
```
The version banner is always printed to stderr (never stdout) to avoid corrupting the JSON-RPC stream in stdio mode.

View File

@ -0,0 +1,144 @@
---
title: "NOAA CO-OPS Data"
description: "Understanding the NOAA Center for Operational Oceanographic Products and Services"
sidebar:
order: 3
---
import { Aside, Card, CardGrid, LinkCard } from "@astrojs/starlight/components";
mcnoaa-tides is a wrapper around NOAA's public tide and current data. Understanding what that data represents, where it comes from, and what its limitations are will help you interpret results correctly and avoid common pitfalls.
## What is CO-OPS?
The **Center for Operational Oceanographic Products and Services** (CO-OPS) is a division of NOAA's National Ocean Service. It maintains the National Water Level Observation Network (NWLON) -- a collection of roughly 301 active coastal tide stations distributed across the United States and its territories.
These stations measure water levels, and many also record meteorological conditions: wind, air temperature, water temperature, barometric pressure, conductivity, visibility, humidity, and salinity. Some stations have been operating continuously for over a century -- Seattle's station (9447130) has records dating to 1899.
CO-OPS provides two things that mcnoaa-tides consumes:
- **Observations** -- real sensor readings from station instruments, updated every 6 minutes
- **Predictions** -- astronomical tide calculations based on harmonic analysis of historical data
The API is public and requires no authentication.
## Station IDs
Every CO-OPS station has a 7-digit numeric identifier. mcnoaa-tides validates this format before making any API request.
| Station ID | Name | State | Notes |
|---|---|---|---|
| 9447130 | Seattle | WA | Operating since 1899 |
| 8454000 | Providence | RI | Narragansett Bay reference station |
| 8723214 | Virginia Key | FL | Biscayne Bay |
| 9414290 | San Francisco | CA | Golden Gate reference station |
Not all stations are tidal. Great Lakes stations measure water levels that fluctuate with wind and barometric pressure rather than gravitational tides. The `tidal` field in station metadata distinguishes these. The `greatlakes` field explicitly flags Great Lakes stations.
<Aside type="caution">
Tide predictions are only meaningful for tidal stations. Great Lakes stations have observations but no tide predictions -- the water level changes are weather-driven, not tidally driven. Requesting predictions for a non-tidal station will return an API error.
</Aside>
## Predictions vs. Observations
This is the most important distinction in the dataset.
<CardGrid>
<Card title="Predictions">
Computed from harmonic analysis of decades of historical water level data. They represent what the tide **should** be doing based on the gravitational pull of the moon and sun, the shape of the coastline, and the bathymetry of the local basin. Predictions are deterministic -- they can be calculated years in advance.
</Card>
<Card title="Observations">
Actual sensor readings from the station's instruments, recorded every 6 minutes. They show what the water **is actually** doing. Observations reflect everything predictions capture plus everything they do not: wind setup, storm surge, barometric pressure effects, river discharge, and seiches.
</Card>
</CardGrid>
The gap between predicted and observed water levels is the core of the `water_level_anomaly` tool. A large positive anomaly (observed well above predicted) can indicate storm surge or sustained onshore wind. A large negative anomaly can indicate offshore wind holding water back. Either way, the deviation from prediction tells you something about non-tidal forces acting on the water.
### When predictions and observations diverge
Under calm conditions with stable barometric pressure, observations and predictions typically agree within a few inches. The divergence grows with:
- **Strong winds** -- sustained onshore wind pushes water levels above predictions (wind setup); offshore wind pulls them below
- **Low barometric pressure** -- the inverse barometer effect raises water levels roughly 1 cm per millibar of pressure drop
- **Storm surge** -- the combination of wind and pressure during storms can push water levels several feet above predictions
- **River discharge** -- heavy rainfall upstream raises water levels at estuarine stations
- **Seiches** -- standing wave oscillations in enclosed or semi-enclosed basins
## Datums
Water level values are meaningless without a vertical reference. Every water level reading -- predicted or observed -- is relative to a **datum**, a fixed reference elevation at the station.
| Datum | Full Name | Description |
|---|---|---|
| MLLW | Mean Lower Low Water | Average of the lower of the two daily low tides over a 19-year epoch. The default datum for navigation charts and mcnoaa-tides. |
| MSL | Mean Sea Level | Average water level over a 19-year epoch. |
| NAVD | North American Vertical Datum (1988) | A geodetic datum tied to the continental land survey network. Used for surveying and flood maps. |
| STND | Station Datum | An arbitrary local reference unique to each station. The most stable reference but not comparable between stations. |
mcnoaa-tides defaults to **MLLW** because it is the standard for U.S. nautical charts and the most intuitive for marine use: a value of 0.0 ft MLLW means the water is at the average low-tide mark, and negative values mean the water is below average low tide.
<Aside type="tip">
If you are comparing water levels across different stations, NAVD is a better choice because it is a consistent geodetic reference. MLLW varies by station based on local tidal range.
</Aside>
## Time Zones
NOAA returns timestamps in the time zone you request. mcnoaa-tides defaults vary by context:
| Context | Default | Reason |
|---|---|---|
| User-facing tools (tides, meteorological, conditions) | `lst_ldt` (local standard/daylight time at the station) | Most intuitive for users planning activities at the station's location |
| SmartPot tools (tidal_phase, deployment_briefing, etc.) | `gmt` (UTC) | GPS coordinates and deployment systems work in UTC |
The timestamp appears in the `"t"` field of each data record, formatted as `"YYYY-MM-DD HH:MM"`. There is no timezone indicator in the timestamp string itself -- you need to know which time zone you requested to interpret it correctly.
<Aside type="caution">
When using `lst_ldt`, timestamps shift with daylight saving time transitions. A 24-hour request spanning a DST boundary will have 23 or 25 hours of data depending on direction. UTC avoids this ambiguity.
</Aside>
## Data Quality
Observations include quality metadata in two fields:
**`q` field -- QA level:**
- `p` = **preliminary** -- real-time data that has not been reviewed. This is what you get for recent observations (last few days to weeks). It may be revised later.
- `v` = **verified** -- post-processed data that has been reviewed for quality. Typically available weeks to months after collection.
**`f` field -- quality flags:**
A comma-separated string of flag codes. `0` means no quality issues detected. Other values indicate specific conditions:
- Sensor range exceeded
- Rate-of-change limit exceeded
- Data gap filled by interpolation
- Flat-line detection (sensor stuck)
For real-time marine planning, preliminary data is perfectly usable -- the revisions during QA are typically small corrections. The flags are more relevant: if you see non-zero flags, the reading may be unreliable.
### Sensor availability
Not every station has every sensor. A station might have a tide gauge but no wind anemometer, or measure water temperature but not air pressure. The `get_station_info` tool returns the full list of available sensors and products for any station.
The parallel-fetch tools (`marine_conditions_snapshot`, `visualize_conditions`) handle missing sensors gracefully -- they report unavailable products in the response rather than failing the entire request.
## Rate Limits and Constraints
The NOAA CO-OPS API is public and does not require authentication, but it does have practical constraints:
| Constraint | Value |
|---|---|
| Maximum date range per request | 720 hours (30 days) |
| Authentication | None required |
| Application identifier | `mcnoaa-tides-mcp` (sent with every request) |
| Rate limiting | Not formally documented; mcnoaa-tides uses a single `httpx.AsyncClient` with connection pooling to avoid hammering the API |
| Response format | JSON (mcnoaa-tides always requests `format=json`) |
The 720-hour (30-day) limit is enforced by the API. Requesting a longer range returns an error. For longer time series, you would need to make multiple requests with consecutive date ranges -- mcnoaa-tides does not do this automatically.
<Aside type="note">
During U.S. government shutdowns, NOAA APIs may become intermittent or unavailable. The station cache's stale-while-revalidate behavior helps maintain station discovery during brief outages, but data fetches will fail if the data API is down.
</Aside>
<LinkCard
title="NOAA CO-OPS API Documentation"
description="Official documentation for the Tides and Currents data and metadata APIs."
href="https://tidesandcurrents.noaa.gov/api/"
/>

View File

@ -0,0 +1,166 @@
---
title: "Tidal Phase Classification"
description: "How the phase classification algorithm determines ebb, flood, and slack conditions"
sidebar:
order: 2
---
import { Aside, Card, CardGrid, Steps } from "@astrojs/starlight/components";
The `tidal_phase` tool does not just look up a value from NOAA. It runs a classification algorithm locally, using NOAA's high/low predictions as input. This page explains how that algorithm works, why it exists, and what its outputs mean.
All of this logic lives in `tidal.py` -- a pure-Python module with no I/O, no async, and no FastMCP dependencies. It takes datetimes and numbers in, and returns classification results out.
## The Four Phases
Tides follow a roughly sinusoidal cycle. At any given moment, the water is doing one of four things:
<CardGrid>
<Card title="Flood">
The tide is rising. Water moves shoreward, into estuaries and harbors. This is the period after a low tide and before the next high tide. Current flows inward.
</Card>
<Card title="Ebb">
The tide is falling. Water moves seaward, draining out of estuaries and harbors. This is the period after a high tide and before the next low tide. Current flows outward.
</Card>
<Card title="Slack High">
The tide has just reached its peak or is about to. Current is minimal -- the water is essentially pausing before reversing direction. This is the transition from flood to ebb.
</Card>
<Card title="Slack Low">
The tide has just reached its trough or is about to. Current is minimal. This is the transition from ebb to flood.
</Card>
</CardGrid>
The classification algorithm maps any timestamp to one of these four phases, plus an `"unknown"` fallback when data is insufficient.
## How Classification Works
The algorithm takes two inputs: a timestamp (`now`) and a list of high/low events from NOAA's hilo predictions. It produces a phase label, human-readable description, timing details, and a progress percentage.
<Steps>
1. **Parse hilo predictions** -- `parse_hilo_predictions()` converts raw NOAA response dicts (with string timestamps and string values) into typed records with Python `datetime` objects and float values. It filters out any entries missing a `"type"` field and sorts by time.
```python
# Input from NOAA
{"t": "2026-02-21 04:30", "v": "4.521", "type": "H"}
# After parsing
{"dt": datetime(2026, 2, 21, 4, 30), "v": 4.521, "type": "H"}
```
2. **Find bracketing events** -- The algorithm scans the sorted event list to find two events: the most recent one before `now` (the previous event) and the first one after `now` (the next event). These two events bracket the current moment in the tidal cycle.
3. **Check slack windows** -- Before classifying ebb or flood, the algorithm checks whether `now` falls within a slack window around any high or low event. This check takes priority because slack periods are short and operationally important.
4. **Classify by bracketing event types** -- If not in a slack window, the phase is determined by what kind of events bracket the current time:
- Previous = High, Next = Low: **ebb** (falling)
- Previous = Low, Next = High: **flood** (rising)
5. **Calculate progress** -- Linear interpolation between the previous and next events gives a percentage indicating how far through the current phase the tide has progressed.
</Steps>
## The Slack Window
The slack window is defined by a single constant:
```python
SLACK_WINDOW_MIN = 30 # minutes
```
If the current time is within 30 minutes of a high tide event (either just past it or approaching it), the phase is `slack_high`. Within 30 minutes of a low tide event, the phase is `slack_low`.
The slack check runs before the ebb/flood classification. This means a timestamp that is 20 minutes after a high tide will be classified as `slack_high`, not `ebb`, even though the water has technically started falling. This is intentional -- from a practical standpoint (navigation, fishing, crabbing), the current near a tidal turning point is negligible regardless of which side of the peak you are on.
<Aside type="tip">
The 30-minute slack window is a reasonable default for most coastal stations. In narrow channels or rivers with strong tidal influence, the actual slack period may be shorter. In wide bays, it may be longer. The constant is not configurable at runtime but could be adjusted in the source if needed.
</Aside>
### Slack priority rules
The algorithm checks slack conditions in this order:
1. Within 30 min **after** a high tide -> `slack_high`
2. Within 30 min **before** a high tide -> `slack_high`
3. Within 30 min **after** a low tide -> `slack_low`
4. Within 30 min **before** a low tide -> `slack_low`
If none of these match, classification falls through to the ebb/flood logic.
## Progress Percentage
When both a previous and next event are known, the algorithm computes a progress percentage through the current phase using linear interpolation:
```
progress_pct = (elapsed / total) * 100
```
Where `elapsed` is the time since the previous event and `total` is the full duration between the previous and next events.
**Example:** High tide occurred at 06:00, next low tide is at 12:12 (6 hours 12 minutes apart). At 09:00, 3 hours have elapsed out of 6.2 total hours.
```
progress_pct = (3.0 / 6.2) * 100 = 48.4%
```
This tells you the tide is about halfway through its ebb phase. A progress of 0% means the phase just started; 100% means the next turning point is imminent.
<Aside type="note">
The progress percentage uses linear interpolation, not a cosine curve. Real tides follow a roughly sinusoidal pattern, so water level change is fastest in the middle of a phase and slowest near the turning points. The progress percentage tracks **time**, not water level change rate.
</Aside>
## Interpolation for Anomaly Detection
The `interpolate_predictions()` function serves a different purpose from phase classification. It takes 6-minute interval predictions (not hilo events) and linearly interpolates the expected water level at any arbitrary timestamp.
The `water_level_anomaly` tool uses this to compare what the water level **should be** (predicted) against what it **actually is** (observed). The difference is the anomaly -- caused by wind, barometric pressure, storm surge, or other non-tidal forces.
```python
def interpolate_predictions(obs_dt, pred_times, pred_values) -> float | None:
```
The function finds the two prediction timestamps that bracket `obs_dt`, then does a linear interpolation between their values. If `obs_dt` falls outside the prediction window entirely, it returns `None`.
### Why linear interpolation is sufficient
NOAA provides 6-minute predictions. Over a 6-minute interval, the tidal curve is nearly linear -- the error from linear interpolation versus a true sinusoidal fit is on the order of millimeters. Since the anomaly detection threshold defaults to 0.5 feet, this precision is more than adequate.
## Edge Cases
The algorithm handles several boundary conditions:
| Situation | Behavior |
|---|---|
| No hilo data at all | Returns `"unknown"` phase with null timing fields |
| Only a previous event (no future event in data) | Infers phase from the last event type: after a high -> `"ebb"`, after a low -> `"flood"` |
| Only a future event (no past event in data) | Infers from the upcoming event type: before a high -> `"flood"`, before a low -> `"ebb"` |
| Neither previous nor future event | Returns `"unknown"` |
| Exactly on a hilo event timestamp | The event is classified as `prev_event` (the `<=` comparison), placing the timestamp within the slack window |
The single-event fallback cases use qualifiers like "likely ebbing" or "likely flooding" in the description to signal reduced confidence. These situations arise when the prediction window is too narrow -- for instance, fetching only 2 hours of hilo data when tidal cycles run 6+ hours.
## Full Classification Output
The `classify_tidal_phase()` function returns a dict with these keys:
```python
{
"phase": "ebb", # ebb | flood | slack_high | slack_low | unknown
"description": "Tide is falling ...", # Human-readable explanation
"previous": { # Most recent past hilo event
"type": "high",
"time": "2026-02-21 06:00",
"level_ft": 4.521,
},
"next": { # Next upcoming hilo event
"type": "low",
"time": "2026-02-21 12:12",
"level_ft": -0.134,
},
"minutes_since_previous": 180, # Minutes since previous event
"minutes_to_next": 192, # Minutes until next event
"progress_pct": 48.4, # Percentage through current phase
}
```
The SmartPot `tidal_phase` tool wraps this output with station info, the current UTC timestamp, and the latest observed water level reading, giving a complete picture of tidal conditions at a location.

159
docs/src/styles/custom.css Normal file
View File

@ -0,0 +1,159 @@
/* mcnoaa-tides Maritime Theme — Deep ocean with teal accents */
/* ── Dark Theme (default) ── */
:root {
--sl-color-accent-low: #0d3b3e;
--sl-color-accent: #1a8a8f;
--sl-color-accent-high: #5ec4c8;
--sl-color-white: #e8f0f0;
--sl-color-gray-1: #c4d4d6;
--sl-color-gray-2: #8fa8ac;
--sl-color-gray-3: #4a6568;
--sl-color-gray-4: #2a3d40;
--sl-color-gray-5: #1a2c2f;
--sl-color-gray-6: #0f1f22;
--sl-color-black: #0a1517;
--sl-color-bg-nav: #0c1b1fdd;
--sl-color-bg-sidebar: #0e1e22ee;
--sl-color-hairline-light: #1a3538;
--sl-color-hairline-shade: #0a1517;
--sl-font: "Inter", sans-serif;
--sl-font-system-mono: "JetBrains Mono", monospace;
--sl-content-width: 72rem;
--sl-nav-height: 3.5rem;
}
/* ── Light Theme ── */
:root[data-theme="light"] {
--sl-color-accent-low: #d4f0f1;
--sl-color-accent: #137275;
--sl-color-accent-high: #0b4d50;
--sl-color-white: #1a2c2f;
--sl-color-gray-1: #2a3d40;
--sl-color-gray-2: #4a6568;
--sl-color-gray-3: #8fa8ac;
--sl-color-gray-4: #c4d4d6;
--sl-color-gray-5: #e4eced;
--sl-color-gray-6: #f0f5f6;
--sl-color-black: #f7fafa;
--sl-color-bg-nav: #f0f5f6ee;
--sl-color-bg-sidebar: #f7fafaee;
--sl-color-hairline-light: #d0dfe0;
--sl-color-hairline-shade: #c0d0d2;
}
/* ── Global ── */
html {
scroll-behavior: smooth;
}
/* ── Headings ── */
.sl-markdown-content h1,
.sl-markdown-content h2,
.sl-markdown-content h3,
.sl-markdown-content h4 {
font-family: "Inter", sans-serif;
font-weight: 600;
letter-spacing: -0.01em;
}
.sl-markdown-content h2 {
border-bottom: 1px solid var(--sl-color-hairline-light);
padding-bottom: 0.4em;
margin-top: 2em;
}
/* ── Links ── */
.sl-markdown-content a {
color: var(--sl-color-accent-high);
text-decoration-color: var(--sl-color-accent);
text-underline-offset: 0.15em;
transition: color 0.15s ease;
}
.sl-markdown-content a:hover {
color: var(--sl-color-accent);
}
/* ── Code blocks ── */
.expressive-code {
--ec-brdCol: var(--sl-color-hairline-light);
--ec-brdRad: 0.4rem;
}
.expressive-code pre {
border: 1px solid var(--sl-color-hairline-light);
}
/* ── Tables ── */
.sl-markdown-content table {
border-collapse: collapse;
width: 100%;
}
.sl-markdown-content th {
border-bottom: 2px solid var(--sl-color-accent);
text-align: left;
padding: 0.5em 0.75em;
font-weight: 600;
}
.sl-markdown-content td {
border-bottom: 1px solid var(--sl-color-hairline-light);
padding: 0.5em 0.75em;
}
/* ── Sidebar ── */
nav.sidebar .top-level > li > details > summary,
nav.sidebar .top-level > li > a {
font-weight: 600;
text-transform: uppercase;
font-size: var(--sl-text-xs);
letter-spacing: 0.06em;
color: var(--sl-color-gray-2);
}
/* ── Badges ── */
.sl-badge {
font-family: var(--sl-font-system-mono);
text-transform: uppercase;
letter-spacing: 0.04em;
font-size: 0.7rem;
}
/* ── Card grid for landing page ── */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1rem;
margin: 1.5rem 0;
}
.card-grid a {
display: block;
padding: 1.25rem;
border: 1px solid var(--sl-color-hairline-light);
border-radius: 0.5rem;
text-decoration: none;
color: inherit;
transition: border-color 0.2s ease, background 0.2s ease;
}
.card-grid a:hover {
border-color: var(--sl-color-accent);
background: var(--sl-color-accent-low);
}
.card-grid a h3 {
margin: 0 0 0.5rem;
font-size: 1rem;
color: var(--sl-color-accent-high);
}
.card-grid a p {
margin: 0;
font-size: 0.9rem;
color: var(--sl-color-gray-2);
}

3
docs/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/strict"
}

View File

@ -1,5 +1,6 @@
"""FastMCP server for NOAA CO-OPS Tides and Currents API."""
import os
import sys
from contextlib import asynccontextmanager
@ -59,4 +60,10 @@ prompts.register(mcp)
def main():
print(f"mcnoaa-tides v{__version__}", file=sys.stderr)
transport = os.environ.get("MCP_TRANSPORT", "stdio")
if transport == "streamable-http":
host = os.environ.get("MCP_HOST", "0.0.0.0")
port = int(os.environ.get("MCP_PORT", "8000"))
mcp.run(transport="streamable-http", host=host, port=port)
else:
mcp.run()