Connects Claude to the USGS Instantaneous Values and Daily Values APIs covering roughly 8,000 stream gages and groundwater monitoring sites across the United States. You get seven tools: discover sites by bounding box or watershed HUC, pull current readings in near real time, fetch historical daily or instantaneous series, and check current conditions against percentile ranks from the full period of record. Large time series automatically spill to DuckDB-backed DataCanvas tables when enabled, so you can run SQL aggregations without truncation. Useful for hydrologic analysis, drought and flood assessment, or any workflow that needs authoritative water data with geographic and temporal filtering. Supports both stdio and streamable HTTP transports, with a public hosted instance available.
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:
{
"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.2 (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_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 and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
USGS_USER_AGENTCustom User-Agent string for USGS NWIS requests. Defaults to a descriptive identifier including the repo URL.
USGS_REQUEST_TIMEOUT_MSdefault: 30000HTTP request timeout in milliseconds for NWIS calls. Default: 30000.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.
com.mcparmory/google-sheets
domdomegg/google-sheets-mcp
henilcalagiya/google-sheets-mcp
cct15/war-dashboard-data
moooonad/mcp-google-sheets-full
io.github.br0ski777/csv-to-json