The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Usgs Water MCP Server listing page.
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
Five tools for querying USGS water data, plus two for SQL analytics over the DuckDB-backed canvas dataframes that water_get_series and water_find_sites materialize:
| 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. |
water_list_parametersStatic lookup of well-known USGS parameter codes — no network call, instant response.
00060 = Discharge (ft³/s), 00065 = Gage height (ft), 00010 = Temperature (°C), 72019 = Depth to water level (ft), and morestreamflow, groundwater, temperature, meteorological, water-quality, or allwater_find_sitesDiscover USGS monitoring sites before calling data tools — all other tools require a site number.
"west,south,east,north" decimal degrees), 2-letter state code, bare 5-digit FIPS county code (e.g. 51013), or HUC watershed code — either a 2-digit major HUC (02) or an 8-digit minor HUC (02070008), the only two lengths NWIS acceptsST (stream), GW (groundwater well), LK (lake/reservoir), SP (spring), and more00060,00065)iv), daily (dv), or groundwater (gw) datatruncated flag and upstreamTotal (the full upstream count) so an oversized query never overflows the responseCANVAS_PROVIDER_TYPE=duckdb is set, the full match set is staged to a DuckDB-backed canvas — the response includes canvas_id and table_name to retrieve every match past the 500 cap via water_dataframe_query. Without DataCanvas, narrow the query with additional filters (county, HUC, bbox, parameter, data type) to bring the result under the capwater_get_readingsGet the latest instantaneous (~15 min) values for one or more USGS monitoring sites.
water_list_parametersPT2H = last 2 hours, P7D = last 7 days)totalValues reporting how many the period actually held and truncated flagging the cap. Use water_get_series when you need the full seriesmissingSites rather than dropped silentlyparameterCd=72019 (the legacy gwlevels endpoint was decommissioned November 2025 — use the IV service instead)water_get_seriesGet a historical time series for a site and parameter over a date range.
CANVAS_PROVIDER_TYPE=duckdb is set — response includes canvas_id and table_name for follow-up SQL via water_dataframe_querytruncated flag and totalRecords countcanvas_id to append data to an existing canvaswater_get_conditionsGet current hydrologic conditions placed in full historical context.
record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05)percentileLabel spelling out the threshold — record-high and record-low mark percentile-of-record extremes, not verified all-time records, and the label says so where the class name does notcomparisonBasis: the reading is instantaneous while the percentiles are approved daily-mean values, so the class is a "how unusual is this" ranking — not a flood-stage or drought determination, which need authoritative thresholds this tool does not fetchsite and parameterCd at the schema edge; a well-formed value NWIS still rejects surfaces as the typed invalid_request reason rather than an opaque upstream errorhistoricalContext: null and a historicalContextStatus saying why — no_record (new/short record), no_matching_day (no row for the date), or unavailable (stat call failed — transient and retryable, kept distinct from a sparse record)water_dataframe_describe / water_dataframe_queryIn-conversation SQL analytics over the dataframes that water_get_series and water_find_sites materialize on a DuckDB-backed canvas — time-series tables from the former, full site match sets from the latter.
Workflow:
water_get_series with a large date range, or water_find_sites with a query that matches more than 500 sites — when DataCanvas is enabled, the response includes canvas_id and table_namewater_dataframe_describe with the canvas_id to confirm the table schema — series tables carry date_time, value, qualifiers, site_number, parameter_cd, unit_code; site tables carry site_number, site_name, site_type, latitude, longitude, huc_cd, and the expanded fieldswater_dataframe_query with the canvas_id and a SELECT statement to run aggregates, filter, or joinRead-only by default — only SELECT statements are permitted. Results are capped at 10,000 rows; a query matching more comes back with truncated: true. Requires CANVAS_PROVIDER_TYPE=duckdb in the server environment.
| Type | Name | Description |
|---|---|---|
| Resource | usgs-water://site/{siteId} | Site metadata: name, coordinates, type, HUC, state, county, drainage area, and altitude |
| Resource | 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.
Built on @cyanheads/mcp-ts-core:
none, jwt, oauthin-memory, filesystem, Supabase, Cloudflare KV/R2/D1USGS NWIS–specific:
water_get_readings accepts up to 100 site numbers in one callwater_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:
percentileClass string (record-high, normal, record-low, etc.) they can act on directly without parsing numeric thresholds, plus a percentileLabel stating the threshold in plain language so the record-* classes are not mistaken for verified all-time recordshistoricalContext: null and a historicalContextStatus that separates an empty stat table (no_record / no_matching_day) from a failed stat call (unavailable, transient), rather than collapsing both into an errorwater_get_readings returns the series it got and names the rest in missingSites, so a silently dropped site never reads as a complete answerwater_get_series reports totalRecords and truncated, water_find_sites reports upstreamTotal and truncated, and water_get_readings reports per-series totalValues plus truncated, so callers know when a preview is incomplete. canvas_id / table_name tell them exactly how to retrieve the reststructuredContent and in the markdown, so neither class of client sees a different answerA public instance is available at https://usgs-water.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
Add the following to your MCP client configuration file.
Or with npx (no Bun required):
Or with Docker:
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:
| 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.3 (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. This project's .env.example and Docker runtime use stateless; 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:
Run checks and tests:
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 and pull requests are welcome. Run checks and tests before submitting:
Apache-2.0 — see LICENSE for details.