Query real-time and historical USGS water data from ~8,000 stream gages and groundwater wells.
Query real-time and historical water data from ~8,000 USGS stream gages and groundwater wells via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://usgs-water.caseyjhand.com/mcp
USGS NWIS water data — ~8,000 active stream gages and groundwater wells across the US and territories. Find monitoring sites, pull the latest readings or a historical time series, and rank current conditions against decades of percentile records from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
water_list_parameters | Static lookup of well-known USGS parameter codes with names, units, and domain. No network call. |
water_find_sites | Find USGS monitoring sites by bounding box, state, county, or HUC watershed. Filter by site type and parameter availability. Large match sets spill to DataCanvas. |
water_get_readings | Get the latest instantaneous values (~15 min real-time) for up to 100 USGS sites. |
water_get_series | Get a time series of daily or instantaneous values for a site over a date range. Large ranges spill to DataCanvas. |
water_get_conditions | Get current hydrologic conditions ranked against the full period-of-record percentile statistics. |
water_dataframe_describe | List tables and columns staged on a DataCanvas by water_get_series or water_find_sites. |
water_dataframe_query | Run a read-only SQL SELECT against the time-series and site tables staged by water_get_series and water_find_sites. |
| Resource | Description |
|---|---|
usgs-water://site/{siteId} | Site metadata: name, coordinates, type, HUC, state, county, drainage area, and altitude |
usgs-water://parameters | Full parameter code catalog (same data as water_list_parameters) |
All resource data is also reachable via tools. Use water_find_sites for geographic site discovery.
water_list_parameters tool00060 (Discharge, ft³/s), 00065 (Gage height, ft), 00010 (Temperature, water, °C), and 72019 (Depth to water level, ft)group filters by thematic domain: streamflow, groundwater, temperature, meteorological, water-quality, or all (default)parameterCd input expects a code from this catalogwater_find_sites tool"west,south,east,north"), 2-letter state code, comma-separated 5-digit FIPS county codes (up to 20), or a HUC watershed code — a 2-digit major HUC or an 8-digit minor HUC, the only two lengths NWIS accepts. Exactly one of the four per call: NWIS rejects a request carrying none or more than one, so the tool refuses it first with a reason naming which rule brokesiteType (ST stream, GW groundwater well, LK lake/reservoir, SP spring, and more — comma-separable), parameterCd (require data availability), and hasDataTypeCd (iv / dv / gw) — these narrow within the geographic scope and cannot stand alonesiteOutput: basic (default) or expanded (adds drainage area and contributing area); altitude appears in both modes when USGS records itlimit (1–500, default 500) and offset page through the match set; truncated means matches remain after the returned window and upstreamTotal holds the full count. An offset at or past the end returns an empty page naming the valid range rather than a not-found errorCANVAS_PROVIDER_TYPE=duckdb set, a match set over 500 sites also stages in full to a canvas (canvas_id/table_name) — inspect the columns with water_dataframe_describe, then retrieve every match with water_dataframe_query. The table name carries the query's scope, site type, and a digest of the full filter set, so re-running a query replaces only its own tablewater_get_readings toolparameterCd filter; period is an ISO 8601 lookback duration, default PT2HtotalValues reports the true count and truncated flags any series that was capped; use water_get_series for full historyP provisional, A approved)missingSites rather than dropped silently72019 — the legacy gwlevels endpoint was decommissioned November 2025water_get_series toolseriesType is daily (DV service, one value/day, default) or instantaneous (IV service, ~15 min), over a startDate–endDate rangetruncated: true and totalRecords holding the full countCANVAS_PROVIDER_TYPE=duckdb set, ranges over 500 records spill the complete series to a canvas (canvas_id/table_name) while the inline records stay the most recent — inspect the columns with water_dataframe_describe, then read the full series with water_dataframe_query. The table name carries the site, parameter code, series type, and both date bounds, so re-running a query replaces only its own table; pass a prior canvas_id to add a table to an existing canvaswater_get_conditions toolrecord-high (≥p95), above-normal (p75–95), normal (p25–75), below-normal (p10–25), low (p05–10), record-low (<p05)percentileLabel spells out each threshold in plain language — the record-high/record-low classes mark percentile-of-record extremes, not verified all-time recordscomparisonBasis discloses the granularity mismatch: the reading is instantaneous while the percentiles are daily-mean, so the ranking is approximate, not a flood-stage or drought determinationhistoricalContext: null and a historicalContextStatus of no_record, no_matching_day, or unavailable (the last is a transient, retryable stat-service failure, not a statement about the site's record)water_dataframe_describe toolwater_get_series or water_find_sites, with per-column name, DuckDB type, and nullabilityrow_count is a DuckDB estimate and may differ from the exact countCANVAS_PROVIDER_TYPE=duckdb — call before water_dataframe_query to confirm the exact table and column nameswater_dataframe_query toolSELECT only, against tables staged by water_get_series or water_find_sites; non-SELECT statements, multiple statements, and system-catalog access (information_schema, pg_catalog, duckdb_*) are all rejectedtruncated: true signals more matched — narrow with WHERE/LIMIT, or run SELECT COUNT(*) for the true totalCANVAS_PROVIDER_TYPE=duckdbusgs-water://site/{siteId} resourceapplication/json site metadata: name, coordinates, type, HUC watershed code, state, county, drainage area, and altitudesiteId is an 8–15 digit USGS site number — discover one via water_find_sitesusgs-water://parameters resourceapplication/json — the same data as water_list_parametersBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
USGS Water-specific:
water_get_series (long date ranges) and water_find_sites (match sets past the 500-site cap) stage the full result as a DuckDB-backed table, queryable via water_dataframe_query72019 — the legacy gwlevels endpoint was decommissioned November 2025Agent-friendly output:
water_get_conditions returns a percentileClass callers can act on directly, paired with a percentileLabel that states the threshold in plain languagewater_get_readings returns the series it got and names the rest in missingSites; water_get_conditions separates an empty stat table (no_record / no_matching_day) from a failed stat call (unavailable, transient) instead of collapsing both into one errorwater_get_series, water_find_sites, water_get_readings) reports its own count and a truncated flag, plus canvas_id / table_name when the rest is retrievable via SQLstructuredContent and in the markdownA public instance is available at https://usgs-water.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"usgs-water-mcp-server": {
"type": "streamable-http",
"url": "https://usgs-water.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"usgs-water-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/usgs-water-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"usgs-water-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/usgs-water-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"usgs-water-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/usgs-water-mcp-server:latest"
]
}
}
}
To enable DataCanvas for SQL analytics over large result sets (time series and site match sets), add CANVAS_PROVIDER_TYPE=duckdb to the env block in any of the configs above.
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/usgs-water-mcp-server.git
cd usgs-water-mcp-server
bun install
cp .env.example .env
# Edit .env to set any optional overrides
| Variable | Description | Default |
|---|---|---|
CANVAS_PROVIDER_TYPE | Set to duckdb to enable DataCanvas spillover for large results from water_get_series and water_find_sites. | — |
USGS_USER_AGENT | Custom User-Agent string sent to USGS NWIS. USGS requests a descriptive User-Agent per their terms. | usgs-water-mcp-server/0.2.5 (contact: https://github.com/cyanheads/usgs-water-mcp-server) |
USGS_REQUEST_TIMEOUT_MS | HTTP request timeout in milliseconds for NWIS calls. | 30000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP session mode. The server declares stateless in code, matching .env.example and the Docker runtime; setting this overrides that, and auto resolves to stateful. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t usgs-water-mcp-server .
docker run --rm -p 3010:3010 usgs-water-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/usgs-water-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). |
src/mcp-server/resources | Resource definitions (*.resource.ts). |
src/services/nwis | NWIS HTTP client — IV, DV, site, and stat endpoints with HTML error detection. |
src/services/canvas | DataCanvas accessor for DuckDB-backed spillover. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/mcp-server/*/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/usgs-water-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-usgs-water-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/usgs-water-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/usgs-water-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.