# io.github.cyanheads/usgs-water-mcp-server (remote · usgs-water.caseyjhand.com)

Query real-time and historical USGS water data from ~8,000 stream gages and groundwater wells.

- Trust score: 71/100 (medium)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `usgs-water.caseyjhand.com`: 71/100 (this document), [markdown](https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water.md), [page](https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water)
- npm · `@cyanheads/usgs-water-mcp-server`: 35/100, [markdown](https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/cyanheads-usgs-water-mcp-server.md), [page](https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/cyanheads-usgs-water-mcp-server)

## Channel facts

- Endpoint: `https://usgs-water.caseyjhand.com/mcp`
- Transports: `streamable-http`
- Auth: `none`
- Version: `0.2.2`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Endpoint Security**: 66/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation not fully verified: no authorisation is required to call this server, and 7 tool(s) never declared a destructiveHint. The MCP spec treats an absent hint as destructive by default, so we cannot call this surface safe.
  - HTTPS is enforced; there's no plaintext access path.
  - The HSTS (Strict-Transport-Security) header is present.
  - DNSSEC is configured correctly; the domain's records validate against the full chain to the root.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 80/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 2330 tokens (~211/item across 11 items; 7 tools + 4 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add --transport http cyanheads-usgs-water-mcp-server https://usgs-water.caseyjhand.com/mcp
```

### Codex

```toml
[mcp_servers.cyanheads-usgs-water-mcp-server]
url = "https://usgs-water.caseyjhand.com/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "cyanheads-usgs-water-mcp-server": {
      "type": "remote",
      "url": "https://usgs-water.caseyjhand.com/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add cyanheads-usgs-water-mcp-server --url https://usgs-water.caseyjhand.com/mcp --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  cyanheads-usgs-water-mcp-server:
    url: "https://usgs-water.caseyjhand.com/mcp"
```

### Other

```json
{
  "mcpServers": {
    "cyanheads-usgs-water-mcp-server": {
      "type": "http",
      "url": "https://usgs-water.caseyjhand.com/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-02 (score 71, +1)

No change was recorded against any check on this day. Stability & Change Management went from 20 to 23. That category is still filling its 30-day observation window: 6 days of observed history at the previous scan, 7 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-31 (score 70, +1)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-29 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-28 (score 68, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-27 (score 67, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-26 (score 67)

First indexed and scored.

## MCP tools (7)

### `water_list_parameters` (~142 tokens)

Water List Parameters

List well-known USGS parameter codes with human-readable names, units, and thematic domain — a static, built-in catalog. Use this first to discover that 00060 = "Discharge" (ft³/s), 00065 = "Gage height" (ft), 00010 = "Temperature, water" (°C), 72019 = "Depth to water level" (ft), etc. Filter by group to narrow results.

Input parameters:

- `group` (string): Filter by thematic domain: "streamflow", "groundwater", "temperature", "meteorological", "water-quality", or "all" (default) for the full catalog.

Output parameters:

- `parameters` (array): Matching parameter records with code, name, unit, and group.
- `total` (number): Number of parameters returned.

### `water_find_sites` (~644 tokens)

Water Find Sites

Find USGS water monitoring sites by bounding box, state, county, or HUC watershed code, filtered by site type and parameter availability. Returns site numbers, names, coordinates, types, altitude, and (in expanded mode) drainage area. Call this first — water_get_readings, water_get_series, and water_get_conditions all require a site number. Capped at 500 sites inline; when truncated=true, upstreamTotal holds the full count and, if DataCanvas is enabled, the complete match set stages to a canvas (canvas_id/table_name) for retrieval via water_dataframe_query — otherwise narrow the filters to get all matches.

Input parameters:

- `bbox` (string): Bounding box as "west,south,east,north" in decimal degrees (e.g. "-77.5,38.5,-76.5,39.5" for the DC metro area). Mutually exclusive with stateCd/countyCd/huc.
- `canvas_id` (string): Canvas ID from a prior call to stage the full match set into an existing canvas rather than creating a new one. Applies only when the result is truncated and DataCanvas is enabled. Omit to start a fr…
- `countyCd` (string): FIPS county code(s) as bare 5-digit numbers — state and county digits concatenated, no separator (e.g. "51013" for Arlington, VA). Comma-separate up to 20 (e.g. "51059,51061"). Use with stateCd for c…
- `hasDataTypeCd` (string): Require sites with data of this type. Common values: "iv" (real-time/instantaneous), "dv" (daily values), "gw" (groundwater). Comma-separate multiple types.
- `huc` (string): Hydrologic Unit Code (HUC) scoping results to a watershed. Either a 2-digit major HUC (e.g. "02" for the Mid-Atlantic region) or an 8-digit minor HUC (e.g. "02070008" for the Middle Potomac). NWIS ac…
- `parameterCd` (string): 5-digit parameter code to require at each returned site (e.g. "00060" for discharge). Use water_list_parameters to discover codes. Comma-separate multiple codes with no spaces (e.g. "00060,00065").
- `siteOutput` (string): "basic" returns core identification fields. "expanded" adds drainage area, altitude, contributing area, and other metadata.
- `siteType` (string): Site type filter. Common codes: "ST" (stream), "GW" (groundwater well), "LK" (lake/reservoir), "SP" (spring), "AT" (atmosphere), "OC" (ocean), "ES" (estuary). Comma-separate multiple types (e.g. "ST,…
- `stateCd` (string): 2-character US state abbreviation (e.g. "VA", "WA"). Returns all sites in the state for the given filters.

Output parameters:

- `canvas_id` (string): Canvas ID for the DataCanvas holding the full, uncapped match set. Present only when truncated=true and DataCanvas is enabled. Pass to water_dataframe_describe then water_dataframe_query to retrieve…
- `filters` (object): Filters applied to this query.
- `notice` (string): Advisory when results were capped — points to the staged canvas when DataCanvas is enabled, otherwise to narrowing filters, for retrieving all matches.
- `sites` (array): Matching USGS monitoring sites (capped at 500 inline; when truncated, upstreamTotal holds the full count and canvas_id/table_name point to the staged full set when DataCanvas is enabled).
- `table_name` (string): DuckDB table name in the canvas holding all matching sites. Present when canvas_id is present. Use as the FROM target in water_dataframe_query SQL.
- `total` (integer): Number of sites returned inline in this response (at most 500).
- `truncated` (boolean): True when the upstream result set exceeded the 500-site cap. Query the full match set via water_dataframe_query when canvas_id is present, or narrow filters (add bbox, countyCd, huc, siteType, parame…
- `upstreamTotal` (integer): Total number of sites matching the query upstream, before the 500-site cap was applied. Equals total when truncated=false.

### `water_get_readings` (~271 tokens)

Water Get Readings

Get the latest instantaneous (~15-min, real-time) values for up to 100 USGS sites in one call — per-site, per-parameter records with timestamp, value, unit, and provisional/approved qualifiers. Each series returns only its 10 most recent records (totalValues reports the true count; truncated=true if any were capped); use water_get_series for a full date-range series. Sites NWIS returns nothing for are listed in missingSites, not dropped silently. Use water_find_sites first to discover site numbers and available parameters.

Input parameters:

- `parameterCd` (array): Parameter codes to return. Omit to get all parameters available at each site. Use water_list_parameters to discover codes.
- `period` (string): ISO 8601 duration for the lookback period (e.g. "PT2H" = last 2 hours, "P1D" = last 1 day, "P7D" = last 7 days). Default: "PT2H" (last 2 hours of readings). Widening it raises totalValues, but each s…
- `sites` (array, required): One or more USGS site numbers to query. Maximum 100 per call.

Output parameters:

- `missingSites` (array): Requested site numbers NWIS returned no series for — the site may not exist, or may not measure the requested parameter(s) in the requested period. Empty when every requested site returned data. Veri…
- `query` (object): Query parameters used for this request.
- `readings` (array): Time series per site+parameter combination.
- `total` (integer): Total number of site+parameter time series returned.
- `truncated` (boolean): True when at least one series held more than 10 records and was capped. Per-series counts are in readings[].totalValues; use water_get_series for the full series.

### `water_get_series` (~326 tokens)

Water Get Series

Get a daily or instantaneous time series for one USGS site and parameter over a date range, as time-ordered value records. Large sets (>500 records) return the most recent 500 with truncated=true; with DataCanvas enabled they instead spill to a canvas (canvas_id/table_name) for SQL via water_dataframe_query. Use water_find_sites and water_list_parameters to resolve inputs.

Input parameters:

- `canvas_id` (string): Canvas ID from a prior water_get_series call to append data to an existing canvas rather than creating a new one. Omit to start a fresh canvas.
- `endDate` (string, required): End date in YYYY-MM-DD format (e.g. "2024-12-31").
- `parameterCd` (string, required): A single 5-digit USGS parameter code (e.g. "00060" for discharge, "00065" for gage height). One code per call — this tool returns one series. Use water_list_parameters to discover available codes.
- `seriesType` (string): "daily" returns one value per day (DV service, typically mean/max/min). "instantaneous" returns ~15-minute readings (IV service). Default: "daily". Use "instantaneous" for high-resolution analysis.
- `site` (string, required): USGS site number (8–15 digits, e.g. "01646500" for Potomac River at Little Falls). Use water_find_sites to discover valid site numbers.
- `startDate` (string, required): Start date in YYYY-MM-DD format (e.g. "2024-01-01").

Output parameters:

- `canvas_id` (string): Canvas ID for the DataCanvas holding the full time series. Present only when truncated=true and DataCanvas is enabled. Pass to water_dataframe_describe then water_dataframe_query.
- `notice` (string): Advisory when the result was truncated — narrow the date range or enable DataCanvas for full access.
- `parameterCd` (string): 5-digit USGS parameter code (e.g. "00060" for discharge).
- `parameterName` (string): Human-readable parameter name with units (e.g. "Streamflow, ft³/s").
- `query` (object): Query parameters used for this request.
- `seriesType` (string): "daily" = one value per day (DV service); "instantaneous" = ~15-minute readings (IV service).
- `siteName` (string): Human-readable USGS site name.
- `siteNumber` (string): USGS site number (8–15 digits, e.g. "01646500").
- `table_name` (string): DuckDB table name in the canvas holding all records. Present when canvas_id is present. Use as the FROM target in water_dataframe_query SQL.
- `totalRecords` (integer): Total number of records in the upstream result set (before any truncation).
- `truncated` (boolean): True when the result exceeds 500 records and was trimmed. Query the full series via water_dataframe_query when canvas_id is present, or narrow the date range.
- `unitCode` (string): Unit of measure for all values in this series (e.g. "ft3/s", "ft").
- `values` (array): Time-ordered value records. Contains all records when not truncated, or the most recent 500 when truncated (no canvas) or a preview slice (with canvas).

### `water_get_conditions` (~209 tokens)

Water Get Conditions

Get a USGS site's current reading ranked against its full period-of-record daily-mean percentiles for the same calendar day — a "how unusual is this" percentileClass (record-high to record-low), not a flood-stage or drought determination (this tool fetches no authoritative thresholds). The reading is instantaneous but the percentiles are daily-mean, so the ranking is approximate (see historicalContext.comparisonBasis). When the record is too short to rank, returns the reading with historicalContext=null instead of an error. Use water_find_sites and water_list_parameters to resolve inputs.

Input parameters:

- `parameterCd` (string, required): 5-digit USGS parameter code (e.g. "00060" for discharge, "00065" for gage height). Use water_list_parameters to discover codes.
- `site` (string, required): USGS site number (8–15 digits, e.g. "01646500" for Potomac River at Little Falls). Use water_find_sites to discover valid site numbers.

Output parameters:

- `currentDateTime` (string): ISO 8601 date-time of the most recent observation.
- `currentValue` (string): Most recent observed value as a string. Empty string when no data is available for the current period.
- `historicalContext`: Historical percentile context for the observation's calendar day. Non-null only when historicalContextStatus is "available"; see that field for why it is otherwise absent.
- `historicalContextStatus` (string): Why historicalContext is or is not populated. 'available': percentiles for the observation's calendar day are present. 'no_matching_day': the stat table has rows but none for that calendar day. 'no_r…
- `note` (string): Informational note explaining why historicalContext is null or incomplete. Absent when full historical context is available.
- `parameterCd` (string): 5-digit USGS parameter code that was queried (e.g. "00060").
- `parameterName` (string): Human-readable parameter name with units (e.g. "Streamflow, ft³/s").
- `qualifiers` (array): Data qualifier codes for the current reading.
- `siteName` (string): Human-readable USGS site name.
- `siteNumber` (string): USGS site number (8–15 digits, e.g. "01646500").
- `unitCode` (string): Unit of measure for currentValue and the historical percentiles (e.g. "ft3/s", "ft").

### `water_dataframe_query` (~243 tokens)

Water Dataframe Query

Run a read-only SQL SELECT against water data tables staged on a DataCanvas by water_get_series or water_find_sites. Workflow: run water_get_series or water_find_sites (get canvas_id + table_name) → water_dataframe_describe (confirm the table and its columns) → water_dataframe_query (SQL analysis). Only SELECT statements are permitted. At most 10,000 rows are returned; a query matching more is capped and the response sets truncated=true — scope with WHERE/LIMIT, and use SELECT COUNT(*) or water_dataframe_describe to learn the true match count. Requires DataCanvas to be enabled on this server instance. Returns an error if DataCanvas is not available.

Input parameters:

- `canvas_id` (string, required): Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas holding the data.
- `sql` (string, required): Read-only SELECT statement. Reference the table by the table_name from water_get_series or water_find_sites; columns vary by source table, so run water_dataframe_describe first for the exact schema.…

Output parameters:

- `row_count` (integer): Number of rows returned in the rows array (up to the 10,000-row cap), not the total matched by the query. When truncated is true the cap was reached, so row_count equals the returned count and underc…
- `rows` (array): Result rows returned (up to 10,000). Column names match the SELECT clause.
- `truncated` (boolean): True when the query matched more rows than the 10,000-row cap and the result was capped — rows and row_count then cover only the first 10,000 matches, and the rest are not in this response. False mea…

### `water_dataframe_describe` (~113 tokens)

Water Dataframe Describe

List tables and columns staged on a DataCanvas by water_get_series or water_find_sites. Call this after water_get_series or water_find_sites returns a canvas_id to discover the exact table name and column types before writing a query. Then pass the table name to water_dataframe_query. Requires DataCanvas to be enabled on this server instance. Returns an error if DataCanvas is not available.

Input parameters:

- `canvas_id` (string, required): Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas to describe.

Output parameters:

- `canvas_id` (string): The canvas ID that was described — pass to water_dataframe_query.
- `tables` (array): Tables and views on this canvas.

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water#diagnostics

## Score history

- 2026-08-03: 71
- 2026-08-02: 71
- 2026-08-01: 70
- 2026-07-31: 70
- 2026-07-30: 69
- 2026-07-29: 69
- 2026-07-28: 68
- 2026-07-27: 67
- 2026-07-26: 67

## Links

- Remote endpoint: https://usgs-water.caseyjhand.com/mcp
- Repository: https://github.com/cyanheads/usgs-water-mcp-server
- Changelog RSS feed: https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water/changelog.json
- HTML version of this page: https://verifymcp.io/servers/cyanheads-usgs-water-mcp-server/usgs-water
