Skip to main content
Glama
wylieswanson

nws-weather-usgs-water-mcp

by wylieswanson
README.md
# NWS Weather + USGS Water MCP

<!-- mcp-name: io.github.wylieswanson/nws-weather-usgs-water-mcp -->

[![CI](https://github.com/wylieswanson/nws-weather-usgs-water-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/wylieswanson/nws-weather-usgs-water-mcp/actions/workflows/ci.yml)

An MCP server combining National Weather Service alerts and forecasts with
modern USGS water data. Weather comes from `api.weather.gov`; water data uses
the official Python `dataretrieval.waterdata` module and APIs under
`api.waterdata.usgs.gov`. It does **not** call legacy
`waterservices.usgs.gov` endpoints.

No API key is required. A persistent local cache reduces repeated calls while
keeping live readings fresh.

## Why this server

- **One MCP for weather and water:** correlate forecasts and alerts with nearby
  gauges without configuring separate services.
- **Current USGS stack:** all water retrieval uses modern Water Data APIs—not
  `waterservices.usgs.gov`.
- **Keyless by default:** NWS needs an identifying `User-Agent`, but neither data
  source requires credentials.
- **Public-API friendly:** bounded queries and source-specific SQLite TTLs reduce
  repeat traffic without letting safety data become stale.
- **MCP-ready results:** every data response is bounded, structured, JSON-safe,
  and explicit about cache state and truncation.

## Weather tools

| Tool | Purpose |
| --- | --- |
| `get_active_alerts` | Active NWS watches, warnings, and advisories for a point |
| `get_point_forecast` | Multi-period NWS point forecast |
| `get_hourly_forecast` | Up to 168 hourly periods with precipitation probability |
| `get_forecast_discussion` | Latest raw Area Forecast Discussion from an NWS office |
| `get_wfo_for_point` | Resolve a point to its NWS office, grid, and zones |

`get_active_alerts` is the highest-priority safety tool. An empty successful
result means NWS reports no active alerts for that point; it is not an error.

## Water tools

The primary tools use domain-friendly names and sensible USGS parameter-code
defaults:

| Tool | Purpose |
| --- | --- |
| `find_gauges` | Find nearby gauges that have a latest reading, with exact distance |
| `search_sites` | Case-insensitive partial site-name search, optionally by state |
| `get_current_flow` | Latest discharge (`00060`) |
| `get_flow_anomaly` | Current discharge compared with this date's median and percentiles |
| `get_flow_anomalies` | Bounded 1-25 site anomaly batch with visible, uncached per-site failures |
| `get_peak_flows` | Annual peaks with all-time, seasonal, and current-flow context |
| `get_daily_flow` | Mean daily discharge (`00060`, statistic `00003`) |
| `get_water_level` | Latest gage height (`00065`) |
| `get_stage_trend` | Rising/steady/falling gage-height trend without mixing time series |
| `get_water_temperature` | Latest water temperature (`00010`) in both °C and °F |
| `get_flood_stage` | Latest gage height plus published action/flood thresholds |
| `get_sun_times` | Local and UTC sunrise, sunset, noon, and civil twilight |

`get_flood_stage` reports only thresholds published in the USGS time-series
metadata. It does not invent a flood threshold when a site has none.

The server also exposes generic tools for monitoring locations, time series,
continuous and daily observations, latest values, field measurements, peaks,
statistics, water-quality samples, reference tables, and collection queryables.
Use `get_server_info` to discover supported collections and common parameter
codes. Use `get_cache_info` and `clear_cache` to inspect or reset local caching.

## Complete tool index

| Category | Tools |
| --- | --- |
| NWS weather and solar | `get_active_alerts`, `get_point_forecast`, `get_hourly_forecast`, `get_forecast_discussion`, `get_wfo_for_point`, `get_sun_times` |
| Focused USGS water | `find_gauges`, `search_sites`, `get_current_flow`, `get_flow_anomaly`, `get_flow_anomalies`, `get_peak_flows`, `get_daily_flow`, `get_water_level`, `get_stage_trend`, `get_water_temperature`, `get_flood_stage` |
| General USGS access | `search_monitoring_locations`, `search_time_series`, `get_observations`, `get_latest_values`, `get_field_data`, `get_statistics`, `get_water_quality_samples`, `lookup_reference_data`, `get_collection_queryables` |
| Server and cache | `get_server_info`, `get_cache_info`, `clear_cache` |

## Install and run

Python 3.10 or newer and [uv](https://docs.astral.sh/uv/) are recommended.

Run directly from the GitHub source without a permanent installation:

```bash
uvx --from git+https://github.com/wylieswanson/nws-weather-usgs-water-mcp.git \
  nws-weather-usgs-water-mcp
```

From a source checkout:

```bash
uv sync --frozen
uv run nws-weather-usgs-water-mcp
```

Run those commands from a source checkout. Press Ctrl-C to stop the stdio
server.

The server uses stdio transport and runs without credentials. GeoPandas is not
required; geometries are returned as JSON coordinate arrays.

Results default to 200 rows and are capped at 5,000 rows. Change the process
cap only when needed:

```bash
USGS_WATERDATA_MAX_ROWS=10000 uv run nws-weather-usgs-water-mcp
```

## MCP client configuration

Use an absolute path to this checkout:

```json
{
  "mcpServers": {
    "nws-weather-usgs-water-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/nws-weather-usgs-water-mcp",
        "run",
        "nws-weather-usgs-water-mcp"
      ],
      "env": {
        "NWS_USER_AGENT": "nws-weather-usgs-water-mcp (you@example.com)"
      }
    }
  }
}
```

Neither API requires a key. NWS requires an identifying `User-Agent`; the
server supplies one automatically, and `NWS_USER_AGENT` lets you provide the
recommended application/contact value. If a deployment later needs higher
USGS limits, add `"API_USGS_PAT": "your_key"` to the same `env` object. That
optional key is sent only to USGS in the `X-Api-Key` header and is never cached.

## Local caching

Successful results are cached in a per-user SQLite database. SQLite provides
safe access when several MCP client processes share the cache, and a cache
failure never prevents a live API request.

| Data | Default TTL |
| --- | ---: |
| NWS active alerts | 60 seconds maximum |
| NWS forecasts and forecast discussions | 15 minutes |
| NWS point-to-grid mapping | 7 days |
| Latest readings, nearby gauges, flood stage, flow anomaly | 10 minutes |
| Stage trend | 5 minutes |
| Annual peak-flow history | 7 days |
| Sun times for a resolved date/timezone | 7 days |
| Continuous observations | 10 minutes |
| Daily observations and USGS statistics | 6 hours |
| Field data and other collections | 30 minutes |
| Samples | 1 hour |
| Site and time-series metadata | 6 hours |
| Reference tables and queryable schemas | 24 hours |

Each tool result includes a `cache` object with `hit`, `age_seconds`, and
`ttl_seconds`. Only successful responses are cached; API errors are not.

| Variable | Purpose |
| --- | --- |
| `NWS_USGS_CACHE_ENABLED=0` | Disable caching |
| `NWS_USGS_CACHE_DIR=/path` | Override the OS-specific cache directory |
| `NWS_USGS_CACHE_MAX_ENTRIES=2000` | Limit stored query results |
| `NWS_USGS_CACHE_TTL_SECONDS=300` | Override defaults; alerts remain capped at 60s |
| `NWS_ALERTS_CACHE_TTL_SECONDS=30` | Override alert TTL, capped at 60 seconds |
| `NWS_FORECAST_CACHE_TTL_SECONDS=900` | Override forecast TTL |
| `NWS_POINTS_CACHE_TTL_SECONDS=604800` | Override point-grid TTL |
| `NWS_DISCUSSION_CACHE_TTL_SECONDS=900` | Override discussion TTL |
| `USGS_LATEST_CACHE_TTL_SECONDS=600` | Override current/latest-value TTL |
| `USGS_STAGE_TREND_CACHE_TTL_SECONDS=300` | Override stage-trend TTL |
| `USGS_PEAKS_CACHE_TTL_SECONDS=604800` | Override annual peak-flow history TTL |
| `USGS_PEAK_MONSOON_MONTHS=7-9` | Calendar months classified as monsoon; all others are cool season |
| `USGS_CONTINUOUS_CACHE_TTL_SECONDS=600` | Override continuous-observation TTL |
| `USGS_DAILY_CACHE_TTL_SECONDS=21600` | Override daily-value TTL |
| `USGS_METADATA_CACHE_TTL_SECONDS=21600` | Override site/time-series metadata TTL |
| `USGS_REFERENCE_CACHE_TTL_SECONDS=86400` | Override reference/queryable TTL |
| `USGS_STATISTICS_CACHE_TTL_SECONDS=21600` | Override statistics TTL |
| `USGS_SAMPLES_CACHE_TTL_SECONDS=3600` | Override water-quality sample TTL |
| `SUN_TIMES_CACHE_TTL_SECONDS=604800` | Override deterministic sun-time TTL |
| `MIN_RELIABLE_MEDIAN_CFS=1.0` | Median floor below which flow-anomaly ratios are marked unreliable |
| `RISING_FT=0.10` | Full-window or recent stage rise classified as rising |
| `FALLING_FT=-0.10` | Full-window or recent stage drop classified as falling |

The `get_cache_info` MCP tool reports the active path and entry count;
`clear_cache` deletes all cached responses.

## Examples

Ask an MCP client:

- “Are there active alerts at 34.62, -111.25?”
- “Give me the next 24 hourly forecast periods at Bull Pen trailhead.”
- “Get the latest Flagstaff forecast discussion.”
- “Find streamflow gauges within 20 miles of 33.45, -112.07.”
- “What is the current flow at USGS site 01646500?”
- “How abnormal is the current flow at Fossil Creek site 09507480?”
- “How does West Clear Creek’s current flow compare with its record flood?”
- “Give me mean daily flow at 01646500 from 2026-06-01 through 2026-06-30.”
- “What are the current gage height and published flood stage at 01646500?”
- “Is West Clear Creek at 09505800 rising over the last six hours?”
- “When are sunrise, sunset, and civil dusk at 34.62, -111.25?”
- “Find sites containing ‘Salt River’ in Arizona.”

Bare USGS site numbers such as `01646500` are automatically normalized to
modern monitoring-location IDs such as `USGS-01646500`. Time-series values can
be provisional, so consumers should retain `approval_status` and `qualifier`.

Flow-anomaly results retain the arithmetic `ratio` at low magnitudes but add
`ratio_reliable` and a percentile-led `flow_state`. When the day-of-year median
is below `MIN_RELIABLE_MEDIAN_CFS`, consumers should rank or alert from
`flow_state` and `percentile_bucket`, not the raw ratio. Classified states are
`high`, `above_normal`, `normal`, `below_normal`, `low`, and `negligible`;
`flow_state` is `null` only when no current observation exists to classify.

Stage-trend results group observations by `time_series_id`, compute each trend
independently, and return only the series with the largest rise. This prevents
primary and bubbler stages at different datums from corrupting change and rate
calculations. `approval_status` and `qualifier` remain attached to the selected
series. `get_water_temperature` retains the upstream `value` and
`unit_of_measure` fields and adds `value_c` and `value_f` conveniences.

Peak-flow results return one normalized discharge peak per water year. The
`metadata.summary` object contains the record peak, its date, years of record,
median annual peak, latest flow, and `current_vs_record`. It also groups those
annual records by the calendar month of `peak_date`: monsoon defaults to
July–September and cool season to October–June. Each season includes its peak,
peak date, median, and qualifying-year count; `current_vs_monsoon_record`
compares the latest flow with the configured monsoon record. Override the
monsoon months with `USGS_PEAK_MONSOON_MONTHS` using comma-separated months or
inclusive ranges such as `6-9`; the cool season is always the complement.
Historical peaks are cached independently from the fresher current-flow
reading.

Sun times are calculated locally with Astral. Coordinates resolve to an IANA
timezone through the bundled timezonefinder data unless `tz` is supplied.
Every event has both `_local` and `_utc` ISO timestamps; no external API is
called and the tool does not estimate a canyon-specific latest safe start.

## Result shape

Data tools return a consistent envelope designed for MCP clients:

```json
{
  "data_type": "current_flow",
  "row_count": 1,
  "columns": ["monitoring_location_id", "time", "value", "unit_of_measure"],
  "max_rows_reached": false,
  "records": [{"monitoring_location_id": "USGS-01646500", "value": 1234}],
  "metadata": {"source_url": "https://api.waterdata.usgs.gov/..."},
  "cache": {"enabled": true, "hit": false, "age_seconds": 0, "ttl_seconds": 600}
}
```

Fields within `records` vary by source collection. `max_rows_reached` tells the
client that it should narrow the query or deliberately request a larger bound.
Missing pandas values serialize as JSON `null`; timestamps use ISO 8601 and
geometries are plain coordinate arrays.

## Data source and stability

This project targets `api.weather.gov` and the versioned modern endpoints at
`api.waterdata.usgs.gov`, including the OGC API `v0` collections. Those APIs
can evolve, so pin this package by release and review the changelog before
upgrading. NWS and USGS remain authoritative; this server only retrieves,
composes, bounds, caches, and serializes their data.

## Development

```bash
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
```

The service layer is separate from the MCP registration layer, allowing unit
tests to validate filtering, row caps, site-ID normalization, and JSON-safe
serialization without network calls.

Other Python services can reuse the same validated operations without starting
an MCP subprocess:

```python
from nws_weather_usgs_water_mcp import operations

result = operations.get_current_flow("09507480")
```

`operations.py` is the supported protocol-neutral facade. `server.py` is the
MCP adapter; HTTP applications should not call it through stdio or JSON-RPC.

See [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
[CHANGELOG.md](CHANGELOG.md) before publishing or contributing. Coding agents
should also follow [AGENTS.md](AGENTS.md); Claude-specific project context is in
[CLAUDE.md](CLAUDE.md).

TDQS

A3.5/5.0

Scored across 29 tools

Disambiguation4/5

Most tools have distinct purposes, e.g., get_current_flow vs get_water_level vs get_water_temperature target different parameters. However, get_flow_anomaly and get_flow_anomalies are similar, and get_observations, get_latest_values, and get_field_data could cause confusion despite detailed descriptions.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (clear, find, get, lookup, search). The most common verb is 'get', and the naming is predictable and uniform throughout.

Tool Count4/5

At 29 tools, the count is slightly high but reasonable for a combined NWS and USGS domain. Each tool covers a specific operation, though some like get_sun_times feel peripheral.

Completeness4/5

The server covers core NWS forecasts/alerts and USGS water data well, including current conditions, historical stats, anomalies, and searches. Minor gaps exist, such as lack of radar/satellite tools, but they are outside the stated domain.

Maintenance

ActivityMaintained
ResponsivenessSyncing