nws-weather-usgs-water-mcp
# NWS Weather + USGS Water MCP
<!-- mcp-name: io.github.wylieswanson/nws-weather-usgs-water-mcp -->
[](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
Scored across 29 tools
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.
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.
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.
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.