usgs-water-mcp-server
Server Details
Query real-time and historical USGS water data from ~8,000 stream gages and groundwater wells.
- Status
- Healthy
- Uptime
- 99.9% over 78 days
- Last Tested
- Transport
- Streamable HTTP · MCP 2025-11-25
- URL
- Repository
- cyanheads/usgs-water-mcp-server
- GitHub Stars
- 2
- Server Listing
- @cyanheads/usgs-water-mcp-server
- ChatGPT
- –
- Claude
- –
TDQS
Scored across 8 tools
The three data-retrieval tools — water_get_readings (latest multi-site instantaneous values), water_get_series (date-range series for one site/parameter), and water_get_conditions (percentile ranking of current reading) — share the 'get current/observed data' space and could be confused at a glance. Descriptions draw clear boundaries (multi-site vs single-site, raw values vs percentile context), so misselection is unlikely but not impossible.
All tools share a 'water_' prefix and are highly readable. Most follow verb_noun (water_get_series, water_find_sites, water_list_parameters), but the dataframe tools invert to noun_verb (water_dataframe_describe, water_dataframe_query), a minor deviation from the established pattern.
Eight tools is well-scoped for a USGS data-retrieval server: discovery (find_sites, list_parameters, site_data_catalog), retrieval (get_readings, get_series, get_conditions), and canvas analysis (describe, query). Each earns its place with no redundancy.
Coverage of the read-only data workflow is strong: site discovery, parameter resolution, catalog introspection, multi-site and single-site retrieval, percentile context, and SQL analysis of staged data. Minor gaps include no explicit tool for export/aggregation beyond DataCanvas SQL, and water_dataframe_drop is referenced but not surfaced as a tool.
Available Tools
8 toolswater_dataframe_describeWater Dataframe DescribeARead-onlyIdempotentInspect
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, or to water_dataframe_drop (when this server enables it) to remove a table you no longer need. Requires DataCanvas to be enabled on this server instance. Returns an error if DataCanvas is not available.
| Name | Required | Description | Default |
|---|---|---|---|
| canvas_id | Yes | Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas to describe. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| tables | No | Tables and views on this canvas. |
| canvas_id | No | The canvas ID that was described — pass to water_dataframe_query. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true and openWorldHint=false, so the safety profile is covered. The description adds genuine environmental context beyond that: DataCanvas must be enabled on the server instance, an error is returned if it is not, and drop is only available 'when this server enables it'. This conditional-capability disclosure is valuable and not in the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then sequencing, then next-step routing, then the environmental requirement. Four tight sentences, each carrying distinct information with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and annotations cover the safety profile. The description still supplies prerequisites, workflow routing, and the DataCanvas-enabled error condition, making it complete for an agent to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single canvas_id parameter is documented in the schema with its origin and purpose. The description adds no syntax, format, or constraint detail beyond what the schema already provides, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('List tables and columns staged on a DataCanvas') and explicitly ties the resource to the tools that create it (water_get_series, water_find_sites). An agent can distinguish it from water_dataframe_query (querying) and water_dataframe_drop (removing) without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use ('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') and names the specific follow-on tools (water_dataframe_query, water_dataframe_drop). Prerequisite and sequencing are fully spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_dataframe_queryWater Dataframe QueryARead-onlyIdempotentInspect
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), then optionally water_dataframe_drop (when this server enables it) to remove a table you are done with. 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.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | 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. Example: SELECT date_time, value FROM water_series_01646500_00060 ORDER BY date_time DESC LIMIT 10 | |
| canvas_id | Yes | Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas holding the data. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | No | Result rows returned (up to 10,000). Column names match the SELECT clause. |
| error | No | Present when the call failed. Absent on success. |
| row_count | No | 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 undercounts the true total. To get the true match count run SELECT COUNT(*) with the same filter, or call water_dataframe_describe for the full row count of the staged table; page large results with LIMIT/OFFSET. |
| truncated | No | 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 means rows and row_count are the complete result for this query. When true, narrow the query with WHERE, page with LIMIT/OFFSET, or run SELECT COUNT(*) with the same filter for the true total. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint, idempotentHint, openWorldHint=false), and the description adds traits the annotations cannot express: SELECT-only enforcement, a 10,000-row return cap with a truncated=true flag, how to measure true match counts, and the DataCanvas-disabled error path.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core capability is front-loaded in the first clause, followed by an arrow-form workflow, then constraints and error conditions. No sentence is filler; each adds a distinct operational fact.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need no explanation, yet the description still surfaces the truncated flag and COUNT(*) workaround. Combined with the prerequisite and the workflow, an agent has everything needed to call this correctly on the first attempt.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are already documented, setting a baseline of 3. The description goes further by explaining that table names come from staging tools, that columns vary by source table, and that WHERE/LIMIT scoping is the way to stay under the truncation cap — practical guidance for the sql parameter beyond its schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: run a read-only SQL SELECT against water data tables staged on a DataCanvas. It distinguishes itself from siblings by naming the producers of that data (water_get_series, water_find_sites) and the adjacent analysis/metadata tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It lays out an explicit ordered workflow (get_series/find_sites → dataframe_describe → dataframe_query → optional dataframe_drop) and names the condition under which drop applies ('when this server enables it'). It also states a hard prerequisite: DataCanvas must be enabled, otherwise an error is returned.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_find_sitesWater Find SitesARead-onlyIdempotentInspect
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. Supply exactly one major filter — bbox, stateCd, countyCd, or huc; siteType, parameterCd, and hasDataTypeCd only narrow within it and cannot stand alone. Page through matches with limit/offset (500 per page); truncated=true means matches remain after the returned window and upstreamTotal holds the full count. When the match set exceeds 500 and DataCanvas is enabled, the complete set also stages to a canvas (canvas_id/table_name) — inspect it with water_dataframe_describe, then retrieve it with water_dataframe_query.
| Name | Required | Description | Default |
|---|---|---|---|
| huc | No | 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 accepts no other lengths. Major filter — supply exactly one of bbox, stateCd, countyCd, huc. | |
| bbox | No | 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). One of the four major filters — bbox, stateCd, countyCd, and huc are mutually exclusive with each other, and exactly one must be supplied. | |
| limit | No | Maximum sites to return inline, 1–500. Default 500 (the inline cap). | |
| offset | No | Number of matching sites to skip before returning results. Page through matches beyond the inline cap by advancing offset by limit. Default 0. | |
| stateCd | No | 2-character US state abbreviation (e.g. "VA", "WA"). Returns all sites in the state for the given filters. Major filter — supply exactly one of bbox, stateCd, countyCd, huc. | |
| countyCd | No | 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"). The 5 digits already encode the state, so the code stands alone. Major filter — supply exactly one of bbox, stateCd, countyCd, huc. | |
| siteType | No | 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,GW"). | |
| canvas_id | No | Canvas ID from a prior call to add this match set as a table on an existing canvas rather than creating a new one. Each distinct filter set gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the match set exceeds the inline cap and DataCanvas is enabled. Omit to start a fresh canvas. | |
| siteOutput | No | "basic" returns core identification fields. "expanded" adds drainage area, altitude, contributing area, and other metadata. | basic |
| parameterCd | No | 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"). | |
| hasDataTypeCd | No | Require sites with data of this type. Common values: "iv" (real-time/instantaneous), "dv" (daily values), "gw" (groundwater). Comma-separate multiple types. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| sites | No | The requested window of matching USGS monitoring sites — the slice starting at offset, at most limit long (500 max). upstreamTotal holds the full match count; canvas_id/table_name point to the staged full set when it exceeded the cap and DataCanvas is enabled. |
| total | No | Number of sites returned inline in this response — at most limit, and 0 when offset is at or past upstreamTotal. |
| notice | No | Advisory about the returned window: the staged canvas and how to read it, the filters to narrow by, the window actually returned, the valid offset range when the request landed past the end of the match set, or the fact that a supplied canvas_id went unused because nothing was staged. |
| filters | No | Filters applied to this query. |
| canvas_id | No | Canvas ID for the DataCanvas holding the full, uncapped match set. Present only when the match set exceeded the 500-site cap and DataCanvas is enabled. Pass to water_dataframe_describe then water_dataframe_query to retrieve sites beyond the inline cap. |
| truncated | No | True when matches remain after the returned window (offset + total < upstreamTotal) — false on the last page, and false for a window starting past the end of the match set, where the notice names the valid offset range instead. Advance offset by limit for the next page, narrow filters (add bbox, countyCd, huc, siteType, parameterCd, or hasDataTypeCd), or when canvas_id is present read the staged set with water_dataframe_describe then water_dataframe_query. |
| table_name | No | 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. |
| upstreamTotal | No | Total number of sites matching the query upstream, before limit/offset windowing. Equals total when the whole match set fits in one window. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/openWorld/idempotent, but the description adds real behavior: the 500-per-page cap, truncated=true meaning matches remain, upstreamTotal holding the full count, and the conditional canvas staging side effect with canvas_id/table_name. It stops short of explaining what a canvas is or the cost of staging, but goes well beyond the safety profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then dependencies, then filter rules, then pagination/canvas. Dense and mostly earned, though the canvas sentence is long and slightly overloaded relative to the core call guidance.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return shape is covered, and the description still summarizes returned fields (site numbers, names, coordinates, types, altitude, drainage area in expanded mode). Dependencies, exclusivity, pagination, and the canvas handoff to water_dataframe_describe/query are all present — nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so each parameter (huc lengths, bbox format, countyCd encoding, siteType codes, canvas_id semantics) is already documented in the schema. The description restates the major-filter exclusivity but adds no format or syntax detail beyond it, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (find) and resource (USGS water monitoring sites) with the scoping dimensions (bbox, state, county, HUC) and filters spelled out. It clearly distinguishes itself from siblings by declaring it is the site-resolution step that other tools depend on.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to call it first because water_get_readings, water_get_series, and water_get_conditions require a site number, and names the four sibling data tools. It also states the constraint that exactly one major filter must be supplied and that siteType/parameterCd/hasDataTypeCd can only narrow within it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_get_conditionsWater Get ConditionsARead-onlyIdempotentInspect
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. A reading NWIS reports as no data (a seasonal, discontinued, dry, or malfunctioning gage) returns an empty currentValue with the qualifiers naming why, and is not ranked. When the site measures the parameter with several sensors (methods), one reading is used and named by methodId/methodDescription — from the sensor its percentile series is described as when one is, otherwise the most recent across them; water_get_readings lists every method. Use water_find_sites and water_list_parameters to resolve inputs.
| Name | Required | Description | Default |
|---|---|---|---|
| site | Yes | USGS site number (8–15 digits, e.g. "01646500" for Potomac River at Little Falls). Use water_find_sites to discover valid site numbers. | |
| parameterCd | Yes | 5-digit USGS parameter code (e.g. "00060" for discharge, "00065" for gage height). Use water_list_parameters to discover codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Informational note explaining why historicalContext is null or incomplete, that NWIS reported no value for the current reading, or which statistics series the percentiles came from when methodMatched is false. Absent when the reading is measured and ranked against a statistics series described exactly as its method. |
| error | No | Present when the call failed. Absent on success. |
| methodId | No | NWIS method ID of the sensor series the current reading comes from. When the site reports the parameter from several, a method with a measured latest reading is preferred, then one whose description a statistics series carries exactly (so reading and percentiles come from one sensor), then the most recent. Null only when NWIS returned the series with no method block. |
| siteName | No | Human-readable USGS site name. |
| unitCode | No | Unit of measure for currentValue and the historical percentiles (e.g. "ft3/s", "ft"). |
| qualifiers | No | Data qualifier codes for the current reading. When currentValue is empty they name why NWIS has no value (e.g. "Ssn" seasonal, "Dis" discontinued, "Dry", "Eqp" equipment malfunction). |
| siteNumber | No | USGS site number (8–15 digits, e.g. "01646500"). |
| parameterCd | No | 5-digit USGS parameter code that was queried (e.g. "00060"). |
| currentValue | No | Most recent observed value as a string. Empty string when NWIS reported no value for the reading — qualifiers then give the reason, and historicalContext does not rank it. A method with a measured latest reading is preferred over one whose latest reading has no value. |
| parameterName | No | Human-readable parameter name with units (e.g. "Streamflow, ft³/s"). |
| currentDateTime | No | ISO 8601 date-time of the most recent observation. |
| historicalContext | No | 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. |
| methodDescription | No | NWIS description of that method (e.g. "From multiparameter sonde", "[(2)]"). Null when NWIS leaves it blank, as it does for the default series at most single-sensor sites. |
| historicalContextStatus | No | 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_matching_method': the stat table covers several sensor series at this site and none is identified by the reported method's description — exactly, or apart from a bracketed label — so no series is picked to rank against (note lists them). 'no_record': the stat table is empty — a new site, or a record too short to compute percentiles. 'unavailable': the statistics service call failed — a transient upstream error, not a statement about the site's record; retry shortly. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent/openWorld annotations, it discloses substantial behavioral context: the ranking is approximate because the reading is instantaneous while percentiles are daily-mean, short records return historicalContext=null rather than an error, no-data gages return an empty currentValue with qualifiers, and multi-sensor sites pick a specific method. These are exactly the non-obvious behaviors an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The key purpose and the "not a flood/drought determination" caveat are front-loaded, and every sentence carries information. The multi-sensor sentence is dense and slightly convoluted, but nothing is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return-value details needn't be restated, yet the description helpfully references key fields (percentileClass, historicalContext.comparisonBasis, currentValue). Combined with the behavioral edge cases covered, an agent has everything needed to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and both parameters (site format, parameterCd format, resolution tools) are fully documented in the schema. The description repeats the find_sites/list_parameters guidance without adding syntax or constraint meaning, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource (get a USGS site's current reading ranked against its full period-of-record daily-mean percentiles) and explicitly delineates what it is not (a flood-stage or drought determination). It also names sibling tools (water_get_readings, water_find_sites, water_list_parameters), so an agent can route to the right tool without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear context for use ("how unusual is this" ranking) and points to water_get_readings for per-method readings and to find_sites/list_parameters for resolving inputs. It lacks an explicit when-to-use-this-vs-water_get_series statement, but the scope is well-enough framed that misuse is unlikely.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_get_readingsWater Get ReadingsARead-onlyIdempotentInspect
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. Omitting parameterCd returns every parameter each site publishes. A site measuring one parameter with several sensors returns one series per method, each named by methodId and methodDescription. At most 100 series return per call — every site that returned data keeps at least one, and totalSeries reports how many NWIS returned; pass parameterCd or split the sites across calls to reach the rest. Each series returns only its 10 most recent records (totalValues reports the true count); truncated=true when either cap applied. 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.
| Name | Required | Description | Default |
|---|---|---|---|
| sites | Yes | One or more USGS site numbers to query. Maximum 100 per call. | |
| period | No | 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 series still returns only its 10 most recent records — use water_get_series to retrieve a full series. | PT2H |
| parameterCd | No | Parameter codes to return. Omit to get every parameter each site publishes — one series per site, parameter, and method, so a large batch can exceed the 100-series cap. Use water_list_parameters to discover codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| query | No | Query parameters used for this request. |
| total | No | Number of site + parameter + method time series returned in readings (at most 100). |
| readings | No | Time series per site + parameter + method combination, in NWIS order (site, then parameter code, then method). A method block NWIS returned empty is omitted when another method of the same site and parameter carries values. At most 100: past that, series are kept round-robin across sites — every site's first series, then every site's second — with a site's series carrying values ahead of its empty ones, a series whose every record is no data counting as empty. |
| truncated | No | True when either cap applied. The series cap: totalSeries > total — pass parameterCd or split the sites across calls. The 10-record cap: some readings[].totalValues > values.length — use water_get_series for the full series. |
| totalSeries | No | Number of site + parameter + method time series before the 100-series cap — an empty method block omitted as described under readings is not counted. Greater than total exactly when the cap dropped series; narrow with parameterCd or split the sites across calls to get the rest. |
| missingSites | No | 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. Verify these with water_find_sites. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent/openWorld annotations by disclosing the operational caps: max 100 series per call, at least one series per returned site, only the 10 most recent records per series, truncated=true when a cap applied, and missingSites for sites with no data. This is exactly the behavioral context an agent needs to interpret partial results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded, opening with the core purpose before moving to cap/truncation semantics. Most sentences carry distinct information, though the cap/truncation behavior is restated across several clauses and could be tightened.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a capped, multi-site read tool with an output schema, the description covers everything an agent needs: discovery prerequisite, cap behavior, truncation signaling, missing-site reporting, and where to go for full series. Return-shape explanation is complementary rather than redundant.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description adds meaning by describing the interaction between omitting parameterCd and hitting the 100-series cap, and by explaining that one parameter with several sensors yields one series per method named by methodId/methodDescription — return-structure detail the schema does not carry.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with precise scope: latest instantaneous (~15-min) values for up to 100 USGS sites, returning per-site/per-parameter records. It explicitly distinguishes itself from sibling water_get_series, so an agent can route without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit alternatives and sequencing: 'Use water_get_series for a full date-range series' and 'Use water_find_sites first to discover site numbers and available parameters.' It also states when to omit or pass parameterCd to avoid the 100-series cap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_get_seriesWater Get SeriesARead-onlyIdempotentInspect
Get a daily or instantaneous time series for one USGS site and parameter over a date range, as time-ordered value records. NWIS can hold several series for one query — one per daily statistic (mean, maximum, minimum) and one per sensor (method); this returns one, reporting its statCd and methodId, and lists the rest in otherSeries so any can be re-requested with the statCd and methodId inputs. By default it returns the daily mean when NWIS returns one with values, and the method with the most records. Large sets (>500 records) return the most recent records inline with truncated=true — the last 500 without DataCanvas, and with DataCanvas enabled the complete series also spills to a canvas (canvas_id/table_name): inspect the staged table with water_dataframe_describe, then read the full series with water_dataframe_query. Use water_find_sites and water_list_parameters to resolve inputs.
| Name | Required | Description | Default |
|---|---|---|---|
| site | Yes | USGS site number (8–15 digits, e.g. "01646500" for Potomac River at Little Falls). Use water_find_sites to discover valid site numbers. | |
| statCd | No | Daily statistic to return, as a 5-digit NWIS code: "00003" mean, "00001" maximum, "00002" minimum. Omit for the mean when NWIS returns one with values, otherwise the first statistic that has values; the response names the one returned and lists the others in otherSeries. Daily series only — instantaneous values carry just "00000". | |
| endDate | Yes | End date in YYYY-MM-DD format (e.g. "2024-12-31"). | |
| methodId | No | NWIS method ID of the sensor series to return — the methodId or an otherSeries[].methodId from a prior response for the same site, parameter, and seriesType. Omit for the method with the most records. IDs differ between daily and instantaneous series and between daily statistics. | |
| canvas_id | No | Canvas ID from a prior call to add this series as a table on an existing canvas rather than creating a new one. Each distinct site, parameter code, series type, date range, and selected statistic and method gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the series spills to a canvas. Omit to start a fresh canvas. | |
| startDate | Yes | Start date in YYYY-MM-DD format (e.g. "2024-01-01"). | |
| seriesType | No | "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. | daily |
| parameterCd | Yes | 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. |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| query | No | Query parameters used for this request. |
| notice | No | Advisory about this result: the staged canvas table and how to read it, the advice to narrow the date range when the series was truncated with no canvas available, or the fact that a supplied canvas_id went unused because nothing was staged. |
| statCd | No | NWIS statistic code of the returned series: "00003" daily mean, "00001" daily maximum, "00002" daily minimum, "00000" for instantaneous values. |
| values | No | Time-ordered value records, oldest first within the slice. Holds every record when truncated is false; when truncated, the most recent records only — the last 500 without DataCanvas, or the last N that fit the inline preview budget when the full series is staged on a canvas. |
| methodId | No | NWIS method ID of the sensor series returned. Pass it back as the methodId input to request this series again. Null only when NWIS returned the series with no method block. |
| siteName | No | Human-readable USGS site name. |
| statName | No | Statistic name NWIS attaches to statCd (e.g. "Mean", "Maximum"). Null when NWIS gives none, as for instantaneous values. |
| unitCode | No | Unit of measure for all values in this series (e.g. "ft3/s", "ft"). |
| canvas_id | No | 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. |
| truncated | No | True when the result exceeds 500 records and only the most recent were returned inline. When canvas_id is present, inspect the staged table with water_dataframe_describe then read the full series with water_dataframe_query; otherwise narrow the date range. |
| seriesType | No | "daily" = one value per day (DV service); "instantaneous" = ~15-minute readings (IV service). |
| siteNumber | No | USGS site number (8–15 digits, e.g. "01646500"). |
| table_name | No | 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. |
| otherSeries | No | Every other statistic × method series NWIS returned for this query — re-request one with its statCd and methodId. Empty when the query produced a single series. A method block with no values over the range is omitted when another method of the same statistic has values. |
| parameterCd | No | 5-digit USGS parameter code (e.g. "00060" for discharge). |
| totalRecords | No | Total number of records in the upstream result set (before any truncation). |
| parameterName | No | Human-readable parameter name with units (e.g. "Streamflow, ft³/s"). |
| methodDescription | No | NWIS description of that method (e.g. "From multiparameter sonde", "7.1 ft from riverbed (top), [Discontinued]"). Null when NWIS leaves it blank, as it does for the default series at most single-sensor sites. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only mark it read-only/idempotent/open-world; the description adds substantial behavior the annotations cannot convey. It discloses the default selection rule (daily mean when present, else the first statistic with values; method with the most records), the truncation threshold (>500 records, truncated=true, last 500 without DataCanvas), and that DataCanvas stages the complete series under canvas_id/table_name. This is exactly the operational context an agent needs before trusting the response.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and scope are front-loaded in the first clause, followed by selection semantics, truncation, and canvas follow-up in a logical order. It is dense for a single paragraph, with the truncation/canvas sentence packing several distinct behaviors, but every sentence carries information an agent needs to invoke the tool correctly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 8-parameter tool with a rich response (otherSeries, statCd, methodId, truncated, canvas_id/table_name) and an output schema present, the description covers the non-obvious cases: defaults, multi-series ambiguity, large-result truncation, and the downstream canvas inspection path. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is already 100%, so the baseline is 3, but the description adds real meaning on top: it explains the statCd fallback chain and that instantaneous series carry only "00000", and that canvas_id determines whether an identical re-run replaces its own table or adds a new one. The only gap is that the interplay between seriesType and which statCd/methodId sets are valid is left to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (get a daily or instantaneous time series) scoped to one USGS site and parameter over a date range, and clarifies that exactly one series is returned even when NWIS holds several. An agent can distinguish it from water_get_readings, water_get_conditions, and the dataframe siblings without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear routing for input resolution (water_find_sites, water_list_parameters) and a follow-up workflow when results spill to a canvas (water_dataframe_describe, then water_dataframe_query). It also advises seriesType=instantaneous for high-resolution analysis. It never states when NOT to use this tool, notably versus the sibling water_get_readings, so the guidance is strong but not exclusionary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_get_site_data_catalogWater Get Site Data CatalogARead-onlyIdempotentInspect
Discover a USGS site’s parameters, sensors, statistics, and UTC record bounds before calling water_get_series. Find site numbers with water_find_sites. Each row is a distinct series; metadata bounds may contain gaps and lag observations. For water_get_series, take YYYY-MM-DD from beginDate/endDate, pass statCd only for daily rows, and obtain methodId from a water_get_series response; timeSeriesId is an OGC identifier, never an NWIS methodId. Rows with null seriesType are other computations and cannot be requested with water_get_series. Follow nextCursor with the same filters and limit until null.
| Name | Required | Description | Default |
|---|---|---|---|
| site | Yes | USGS site number (8–15 digits), from water_find_sites; e.g. "01646500". | |
| limit | No | Maximum metadata rows per page (1–1000; default 100). Keep unchanged when using cursor. | |
| cursor | No | Opaque nextCursor from the previous page; keep site, parameterCd, seriesType, and limit unchanged. | |
| seriesType | No | Limit to Daily computations or Points/Instantaneous computations. Omit to include all periods. | |
| parameterCd | No | Optional 5-digit parameter code from water_list_parameters, e.g. "00060". |
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | Present when the call failed. Absent on success. |
| series | No | One metadata page; distinct sensors/statistics remain separate. Empty when a known site has no matching rows. |
| siteName | No | Upstream site name, or null when unavailable. |
| nextCursor | No | Continuation for the same filters and limit, or null when no next page exists. |
| siteNumber | No | USGS site number, preserving leading zeros. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/idempotent/openWorld. The description adds genuine behavioral traits the annotations cannot: metadata bounds may contain gaps and lag observations, null seriesType rows are unrelated computations, and timeSeriesId is an OGC identifier rather than an NWIS methodId — a distinction that prevents a concrete mis-invocation downstream.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and the key 'before calling water_get_series' routing, and no sentence is filler. Slightly dense: several sentences explain how to feed results into a different tool, which crowds the primary purpose, though that guidance has nowhere else to live.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not describe return values, and it covers everything else an agent needs: prerequisites, pagination contract, filter semantics, and the data-quality caveats that affect interpretation. Nothing essential to correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents site, limit, cursor, seriesType, and parameterCd (including enum and pattern). The description mostly adds cross-tool mapping advice for water_get_series's parameters (statCd, methodId) rather than meaning for this tool's own inputs; the cursor chaining note adds only marginal value beyond the schema's 'keep unchanged' wording.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ('Discover') plus resource ('a USGS site's parameters, sensors, statistics, and UTC record bounds') with an explicit scope qualifier ('before calling water_get_series'). It clearly distinguishes itself from water_get_series (catalog/metadata vs. data) and water_find_sites (site discovery).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly sequences sibling tools: use water_find_sites to find site numbers, call this before water_get_series, and follow nextCursor with identical filters until null. It even states a when-not: rows with null seriesType cannot be requested via water_get_series.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
water_list_parametersWater List ParametersARead-onlyIdempotentInspect
Look up USGS parameter codes — the 5-digit codes every other tool's parameterCd takes. With no query, lists a curated set of well-known codes with names, units, and thematic group, from a built-in table (no network call): 00060 = "Discharge" (ft³/s), 00065 = "Gage height" (ft), 00010 = "Temperature, water" (°C), 72019 = "Depth to water level" (ft), and others; filter it with group. For anything else — turbidity, nitrate, chlorophyll, suspended sediment, salinity — pass query to search the full USGS parameter-code catalog (~19,600 codes) by name and description, or pass a 5-digit code to look it up. A query returns at most 25 entries, matching curated codes first, with total counting every match.
| Name | Required | Description | Default |
|---|---|---|---|
| group | No | Filter the curated list by thematic domain: "streamflow", "groundwater", "temperature", "meteorological", "water-quality", or "all" (default). Applies to the curated list only — leave it "all" when passing query. | all |
| query | No | Search the full USGS parameter-code catalog. Case-insensitive; every word must start a word in the parameter name or description (e.g. "turbidity", "nitrate filtered", "dissolved oxygen"). A bare 5-digit code (e.g. "63680") returns that code. Omit to list the curated codes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | Guidance when a query matched nothing or was truncated. Absent otherwise. |
| error | No | Present when the call failed. Absent on success. |
| query | No | The catalog query searched, trimmed. Absent when listing the curated codes. |
| total | No | Number of entries that matched, before the 25-entry cap. |
| truncated | No | True when a query matched more than 25 entries and only the first 25 are listed — add words to narrow it. |
| parameters | No | Matching parameter entries. Without query: the curated codes. With query: matching curated codes first (each once, as its curated entry), then catalog records whose name holds every query word, then those matched through the description, each by code — at most 25. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, and idempotentHint. The description adds meaningful behavioral detail beyond those: the curated list is served from a built-in table with no network call, the query searches a ~19,600-code catalog, and results are capped at 25 with curated matches ranked first and a total count of all matches. This gives the agent concrete expectations about performance and result shape. It doesn't cover error handling or authentication, but for a read-only lookup that is acceptable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but information-dense and logically structured: purpose statement, no-query mode, query mode, code-lookup mode, and result-limit note. Every sentence earns its place; examples are illustrative rather than filler. It is not excessively verbose, though it could be tightened slightly without losing value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present (per context signals), the description does not need to enumerate return fields. It fully covers all input scenarios (no query, query, bare code), explains the effect of group, and states the result cap and counting behavior. For a two-parameter, zero-required lookup tool, nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema itself is unusually thorough for both parameters. The description still adds value by giving concrete examples of curated codes (00060, 00065, 00010, 72019) and by clarifying that query can take a bare 5-digit code or a phrase, plus the 25-entry limit and ordering. These enrich the agent's understanding beyond the schema's already-detailed field descriptions, justifying a score above the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-resource pair ('Look up USGS parameter codes') and immediately frames its role as the source for the 5-digit parameterCd used by every other tool, distinguishing it from siblings that consume those codes. It goes on to enumerate three distinct modes (list curated, search catalog, direct code lookup), leaving no doubt about what the tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit usage conditions are given for each mode: 'With no query, lists a curated set... filter it with group'; 'For anything else... pass query to search'; 'or pass a 5-digit code to look it up.' It also tells the agent when to leave group at 'all' (in the schema) and sets expectations on result limits. This is unambiguous when-to-use guidance with no need to infer.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
- Changed
water_find_sites2 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_sites_found`: No sites match the given geographic and filter criteria. `missing_major_filter`: None of bbox, stateCd, countyCd, or huc was supplied. NWIS scopes every site query by exactly one of them; siteType, parameterCd, and hasDataTypeCd only narrow within that scope. `conflicting_major_filters`: More than one of bbox, stateCd, countyCd, and huc was supplied. NWIS accepts exactly one per request. `invalid_request`: NWIS rejected the request. Filter formats are pattern-validated and the major-filter rule is enforced before the call, so this surfaces a well-formed value NWIS still refused — an unknown state, county, HUC, parameter, or site-type code. `upstream_error`: NWIS returned a 5xx error or the request timed out. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the match set, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_sites_found`: No sites match the given geographic and filter criteria. `missing_major_filter`: None of bbox, stateCd, countyCd, or huc was supplied. NWIS scopes every site query by exactly one of them; siteType, parameterCd, and hasDataTypeCd only narrow within that scope. `conflicting_major_filters`: More than one of bbox, stateCd, countyCd, and huc was supplied. NWIS accepts exactly one per request. `invalid_request`: NWIS rejected the request. Filter formats are pattern-validated and the major-filter rule is enforced before the call, so this surfaces a well-formed value NWIS still refused — an unknown state, county, HUC, parameter, or site-type code. `upstream_error`: NWIS returned a 5xx error. `upstream_timeout`: The NWIS request exceeded its deadline. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the match set, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_sites_found", - "missing_major_filter", - "conflicting_major_filters", - "invalid_request", - "upstream_error", - "canvas_not_found", - "canvas_capacity_exhausted" -]New value: +[ + "no_sites_found", + "missing_major_filter", + "conflicting_major_filters", + "invalid_request", + "upstream_error", + "upstream_timeout", + "canvas_not_found", + "canvas_capacity_exhausted" +]
- Changed
water_get_conditions2 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no IV data — the site may not exist, or may not measure the requested parameter. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: The NWIS IV endpoint returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. A stat-endpoint failure does not raise this — it is reported as historicalContextStatus \"unavailable\". Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no IV data — the site may not exist, or may not measure the requested parameter. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: The NWIS IV endpoint returned a 5xx error or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. A stat-endpoint failure does not raise this — it is reported as historicalContextStatus \"unavailable\". `upstream_timeout`: The NWIS IV request exceeded its deadline. A statistics timeout is reported as historicalContextStatus \"unavailable\". Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_data_for_parameter", - "invalid_request", - "upstream_error" -]New value: +[ + "no_data_for_parameter", + "invalid_request", + "upstream_error", + "upstream_timeout" +]
- Changed
water_get_readings2 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no time series — the site(s) may not exist, or may not have data for the requested parameter(s) in the requested period. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no time series — the site(s) may not exist, or may not have data for the requested parameter(s) in the requested period. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. `upstream_timeout`: The NWIS request exceeded its deadline. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_data_for_parameter", - "invalid_request", - "upstream_error" -]New value: +[ + "no_data_for_parameter", + "invalid_request", + "upstream_error", + "upstream_timeout" +]
- Changed
water_get_series2 fields changed- changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_stat_cd`: statCd is not a 5-digit NWIS statistic code. Raised before the NWIS request. `stat_cd_for_instantaneous`: statCd names a daily statistic but seriesType is \"instantaneous\", whose values carry only statistic \"00000\". Raised before the NWIS request. `method_not_found`: No series NWIS returned for this query carries the requested methodId (within the requested statCd, when one is given), or that method returned no values over the range. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the series, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_stat_cd`: statCd is not a 5-digit NWIS statistic code. Raised before the NWIS request. `stat_cd_for_instantaneous`: statCd names a daily statistic but seriesType is \"instantaneous\", whose values carry only statistic \"00000\". Raised before the NWIS request. `method_not_found`: No series NWIS returned for this query carries the requested methodId (within the requested statCd, when one is given), or that method returned no values over the range. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. `upstream_timeout`: The NWIS request exceeded its deadline. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the series, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_data_for_range", - "invalid_date_range", - "invalid_stat_cd", - "stat_cd_for_instantaneous", - "method_not_found", - "invalid_request", - "upstream_error", - "canvas_not_found", - "canvas_capacity_exhausted" -]New value: +[ + "no_data_for_range", + "invalid_date_range", + "invalid_stat_cd", + "stat_cd_for_instantaneous", + "method_not_found", + "invalid_request", + "upstream_error", + "upstream_timeout", + "canvas_not_found", + "canvas_capacity_exhausted" +]
- Added
water_get_site_data_catalog
4 tool updates
- Changed
water_get_conditions10 fields changed- changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "currentValue", - "currentDateTime", - "qualifiers", - "historicalContext", - "historicalContextStatus" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "methodId", + "methodDescription", + "currentValue", + "currentDateTime", + "qualifiers", + "historicalContext", + "historicalContextStatus" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / currentValue / descriptionPrevious value: -"Most recent observed value as a string. Empty string when no data is available for the current period."New value: +"Most recent observed value as a string. Empty string when NWIS reported no value for the reading — qualifiers then give the reason, and historicalContext does not rank it. A method with a measured latest reading is preferred over one whose latest reading has no value." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no IV data — the site may not exist, or may not measure the requested parameter. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS IV or stat endpoint returned a 5xx error or timed out. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no IV data — the site may not exist, or may not measure the requested parameter. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: The NWIS IV endpoint returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. A stat-endpoint failure does not raise this — it is reported as historicalContextStatus \"unavailable\". Other values are possible when a failure originates below the handler." - changed
Output schema / properties / historicalContext / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "comparisonBasis": { - "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null.", - "type": "string" - }, - "p05": { - "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable.", - "type": [ - "number", - "null" - ] - }, - "p10": { - "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable.", - "type": [ - "number", - "null" - ] - }, - "p25": { - "description": "25th percentile (lower quartile) in unitCode. Null if unavailable.", - "type": [ - "number", - "null" - ] - }, - "p50": { - "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable.", - "type": [ - "number", - "null" - ] - }, - "p75": { - "description": "75th percentile (upper quartile) in unitCode. Null if unavailable.", - "type": [ - "number", - "null" - ] - }, - "p95": { - "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable.", - "type": [ - "number", - "null" - ] - }, - "percentileClass": { - "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", - "enum": [ - "record-high", - "above-normal", - "normal", - "below-normal", - "low", - "record-low", - "unknown" - ], - "type": "string" - }, - "percentileLabel": { - "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not.", - "type": "string" - }, - "periodOfRecord": { - "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", - "type": "string" - } - }, - "required": [ - "percentileClass", - "percentileLabel", - "p05", - "p10", - "p25", - "p50", - "p75", - "p95", - "periodOfRecord", - "comparisonBasis" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "comparisonBasis": { + "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null.", + "type": "string" + }, + "methodMatched": { + "description": "True when statSeriesDescription equals the reported methodDescription, which is how the percentiles are matched to the sensor that produced the reading. False when the descriptions differ but the series was still used — it is the only statistics series whose description matches once the bracketed label the statistics service appends (e.g. \"[BASE GAGE]\") is set aside, or the only statistics series at the site; note says which. The percentiles may then come from a different sensor or location than the reading.", + "type": "boolean" + }, + "p05": { + "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable.", + "type": [ + "number", + "null" + ] + }, + "p10": { + "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p25": { + "description": "25th percentile (lower quartile) in unitCode. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p50": { + "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p75": { + "description": "75th percentile (upper quartile) in unitCode. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p95": { + "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "percentileClass": { + "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). Decided by the thresholds NWIS published: with p95 blank a value ≥ p75 is above-normal, and with p10 or p05 blank a value below p25 or p10 is below-normal or low. normal requires both p25 and p75; a value the published thresholds cannot place is unknown, as is a reading NWIS reported as no data (currentValue empty). See percentileLabel for the threshold in plain language.", + "enum": [ + "record-high", + "above-normal", + "normal", + "below-normal", + "low", + "record-low", + "unknown" + ], + "type": "string" + }, + "percentileLabel": { + "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). When the class was decided without an unpublished threshold, the label names it (e.g. \"≥ 75th percentile; 95th not published\") — the value may lie past it. An unknown class names the published thresholds either side and the blank ones between (e.g. \"25th–95th percentile; 75th not published\"), or, for a reading with no value, says so and names its qualifiers. The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not.", + "type": "string" + }, + "periodOfRecord": { + "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", + "type": "string" + }, + "statSeriesDescription": { + "description": "Location description NWIS gives that statistics series (e.g. \"From multiparameter sonde\"). Null when blank.", + "type": [ + "string", + "null" + ] + }, + "statSeriesId": { + "description": "NWIS statistics time-series ID (ts_id) the percentiles were computed from. A daily-mean series ID, numbered independently of the IV methodId. Null when NWIS omits it.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "percentileClass", + "percentileLabel", + "p05", + "p10", + "p25", + "p50", + "p75", + "p95", + "periodOfRecord", + "comparisonBasis", + "statSeriesId", + "statSeriesDescription", + "methodMatched" + ], + "type": "object" + }, + { + "type": "null" + } +] - changed
Output schema / properties / historicalContextStatus / descriptionPrevious value: -"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_record': the stat table is empty — a new site, or a record too short to compute percentiles. 'unavailable': the statistics service call failed — a transient upstream error, not a statement about the site's record; retry shortly."New value: +"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_matching_method': the stat table covers several sensor series at this site and none is identified by the reported method's description — exactly, or apart from a bracketed label — so no series is picked to rank against (note lists them). 'no_record': the stat table is empty — a new site, or a record too short to compute percentiles. 'unavailable': the statistics service call failed — a transient upstream error, not a statement about the site's record; retry shortly." - changed
Output schema / properties / historicalContextStatus / enumPrevious value: -[ - "available", - "no_matching_day", - "no_record", - "unavailable" -]New value: +[ + "available", + "no_matching_day", + "no_matching_method", + "no_record", + "unavailable" +] - added
Output schema / properties / methodDescriptionAdded value: +{ + "description": "NWIS description of that method (e.g. \"From multiparameter sonde\", \"[(2)]\"). Null when NWIS leaves it blank, as it does for the default series at most single-sensor sites.", + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / methodIdAdded value: +{ + "description": "NWIS method ID of the sensor series the current reading comes from. When the site reports the parameter from several, a method with a measured latest reading is preferred, then one whose description a statistics series carries exactly (so reading and percentiles come from one sensor), then the most recent. Null only when NWIS returned the series with no method block.", + "type": [ + "string", + "null" + ] +} - changed
Output schema / properties / note / descriptionPrevious value: -"Informational note explaining why historicalContext is null or incomplete. Absent when full historical context is available."New value: +"Informational note explaining why historicalContext is null or incomplete, that NWIS reported no value for the current reading, or which statistics series the percentiles came from when methodMatched is false. Absent when the reading is measured and ranked against a statistics series described exactly as its method." - changed
Output schema / properties / qualifiers / descriptionPrevious value: -"Data qualifier codes for the current reading."New value: +"Data qualifier codes for the current reading. When currentValue is empty they name why NWIS has no value (e.g. \"Ssn\" seasonal, \"Dis\" discontinued, \"Dry\", \"Eqp\" equipment malfunction)."
- Changed
water_get_readings12 fields changed- changed
Input schema / properties / parameterCd / descriptionPrevious value: -"Parameter codes to return. Omit to get all parameters available at each site. Use water_list_parameters to discover codes."New value: +"Parameter codes to return. Omit to get every parameter each site publishes — one series per site, parameter, and method, so a large batch can exceed the 100-series cap. Use water_list_parameters to discover codes." - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "readings", - "total", - "truncated", - "missingSites", - "query" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "readings", + "total", + "totalSeries", + "truncated", + "missingSites", + "query" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no time series — the site(s) may not exist, or may not have data for the requested parameter(s) in the requested period. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no time series — the site(s) may not exist, or may not have data for the requested parameter(s) in the requested period. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / readings / descriptionPrevious value: -"Time series per site+parameter combination."New value: +"Time series per site + parameter + method combination, in NWIS order (site, then parameter code, then method). A method block NWIS returned empty is omitted when another method of the same site and parameter carries values. At most 100: past that, series are kept round-robin across sites — every site's first series, then every site's second — with a site's series carrying values ahead of its empty ones, a series whose every record is no data counting as empty." - changed
Output schema / properties / readings / items / descriptionPrevious value: -"Time series result for one site+parameter combination."New value: +"Time series result for one site + parameter + method combination." - added
Output schema / properties / readings / items / properties / methodDescriptionAdded value: +{ + "description": "NWIS description of the method, e.g. \"From multiparameter sonde\", \"[(2)]\", or \"7.1 ft from riverbed (top), [Discontinued]\". Null when NWIS leaves it blank, as it does for the default series at most single-sensor sites.", + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / readings / items / properties / methodIdAdded value: +{ + "description": "NWIS method ID of this series. A site can measure one parameter with several sensors or at several locations — each is its own method and its own entry in readings. Null only when NWIS returned the series with no method block.", + "type": [ + "string", + "null" + ] +} - changed
Output schema / properties / readings / items / properties / values / items / properties / value / descriptionPrevious value: -"Measured value as a string (empty string means no data for that interval)."New value: +"Measured value as a string. Empty string when NWIS reported no value for that interval — qualifiers then give the reason (e.g. \"Ssn\" seasonal, \"Dis\" discontinued, \"Dry\", \"Eqp\" equipment malfunction)." - changed
Output schema / properties / readings / items / requiredPrevious value: -[ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "values", - "totalValues" -]New value: +[ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "methodId", + "methodDescription", + "values", + "totalValues" +] - changed
Output schema / properties / total / descriptionPrevious value: -"Total number of site+parameter time series returned."New value: +"Number of site + parameter + method time series returned in readings (at most 100)." - added
Output schema / properties / totalSeriesAdded value: +{ + "description": "Number of site + parameter + method time series before the 100-series cap — an empty method block omitted as described under readings is not counted. Greater than total exactly when the cap dropped series; narrow with parameterCd or split the sites across calls to get the rest.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / truncated / descriptionPrevious value: -"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."New value: +"True when either cap applied. The series cap: totalSeries > total — pass parameterCd or split the sites across calls. The 10-record cap: some readings[].totalValues > values.length — use water_get_series for the full series."
- Changed
water_get_series14 fields changed- changed
Input schema / properties / canvas_id / descriptionPrevious value: -"Canvas ID from a prior call to add this series as a table on an existing canvas rather than creating a new one. Each distinct site, parameter code, series type, and date range gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the series spills to a canvas. Omit to start a fresh canvas."New value: +"Canvas ID from a prior call to add this series as a table on an existing canvas rather than creating a new one. Each distinct site, parameter code, series type, date range, and selected statistic and method gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the series spills to a canvas. Omit to start a fresh canvas." - added
Input schema / properties / methodIdAdded value: +{ + "description": "NWIS method ID of the sensor series to return — the methodId or an otherSeries[].methodId from a prior response for the same site, parameter, and seriesType. Omit for the method with the most records. IDs differ between daily and instantaneous series and between daily statistics.", + "type": "string" +} - added
Input schema / properties / statCdAdded value: +{ + "description": "Daily statistic to return, as a 5-digit NWIS code: \"00003\" mean, \"00001\" maximum, \"00002\" minimum. Omit for the mean when NWIS returns one with values, otherwise the first statistic that has values; the response names the one returned and lists the others in otherSeries. Daily series only — instantaneous values carry just \"00000\".", + "type": "string" +} - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "seriesType", - "values", - "totalRecords", - "truncated", - "query" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "seriesType", + "statCd", + "statName", + "methodId", + "methodDescription", + "values", + "totalRecords", + "truncated", + "otherSeries", + "query" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the series, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_stat_cd`: statCd is not a 5-digit NWIS statistic code. Raised before the NWIS request. `stat_cd_for_instantaneous`: statCd names a daily statistic but seriesType is \"instantaneous\", whose values carry only statistic \"00000\". Raised before the NWIS request. `method_not_found`: No series NWIS returned for this query carries the requested methodId (within the requested statCd, when one is given), or that method returned no values over the range. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error, timed out, or sent a response body that is not valid WaterML-JSON (cut off mid-document), and retrying did not clear it. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the series, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_data_for_range", - "invalid_date_range", - "invalid_request", - "upstream_error", - "canvas_not_found", - "canvas_capacity_exhausted" -]New value: +[ + "no_data_for_range", + "invalid_date_range", + "invalid_stat_cd", + "stat_cd_for_instantaneous", + "method_not_found", + "invalid_request", + "upstream_error", + "canvas_not_found", + "canvas_capacity_exhausted" +] - added
Output schema / properties / methodDescriptionAdded value: +{ + "description": "NWIS description of that method (e.g. \"From multiparameter sonde\", \"7.1 ft from riverbed (top), [Discontinued]\"). Null when NWIS leaves it blank, as it does for the default series at most single-sensor sites.", + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / methodIdAdded value: +{ + "description": "NWIS method ID of the sensor series returned. Pass it back as the methodId input to request this series again. Null only when NWIS returned the series with no method block.", + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / otherSeriesAdded value: +{ + "description": "Every other statistic × method series NWIS returned for this query — re-request one with its statCd and methodId. Empty when the query produced a single series. A method block with no values over the range is omitted when another method of the same statistic has values.", + "items": { + "additionalProperties": false, + "description": "One series NWIS returned for this query that was not selected.", + "properties": { + "methodDescription": { + "description": "NWIS description of the method; null when blank.", + "type": [ + "string", + "null" + ] + }, + "methodId": { + "description": "NWIS method ID of this series — pass as the methodId input.", + "type": [ + "string", + "null" + ] + }, + "recordCount": { + "description": "Number of value records this series holds over the requested range.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "statCd": { + "description": "NWIS statistic code of this series (e.g. \"00001\").", + "type": "string" + }, + "statName": { + "description": "Statistic name for statCd (e.g. \"Maximum\"); null when NWIS gives none.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "statCd", + "statName", + "methodId", + "methodDescription", + "recordCount" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / query / properties / methodIdAdded value: +{ + "description": "Method ID requested, if any.", + "type": "string" +} - added
Output schema / properties / query / properties / statCdAdded value: +{ + "description": "Statistic code requested, if any.", + "type": "string" +} - added
Output schema / properties / statCdAdded value: +{ + "description": "NWIS statistic code of the returned series: \"00003\" daily mean, \"00001\" daily maximum, \"00002\" daily minimum, \"00000\" for instantaneous values.", + "type": "string" +} - added
Output schema / properties / statNameAdded value: +{ + "description": "Statistic name NWIS attaches to statCd (e.g. \"Mean\", \"Maximum\"). Null when NWIS gives none, as for instantaneous values.", + "type": [ + "string", + "null" + ] +} - changed
Output schema / properties / values / items / properties / value / descriptionPrevious value: -"Measured value as a string (empty string means no data for that interval)."New value: +"Measured value as a string. Empty string when NWIS reported no value for that interval — qualifiers then give the reason (e.g. \"Ssn\" seasonal, \"Dis\" discontinued, \"Dry\", \"Eqp\" equipment malfunction). A staged canvas table holds NULL for these."
- Changed
water_list_parameters19 fields changed- changed
Input schema / properties / group / descriptionPrevious value: -"Filter by thematic domain: \"streamflow\", \"groundwater\", \"temperature\", \"meteorological\", \"water-quality\", or \"all\" (default) for the full catalog."New value: +"Filter the curated list by thematic domain: \"streamflow\", \"groundwater\", \"temperature\", \"meteorological\", \"water-quality\", or \"all\" (default). Applies to the curated list only — leave it \"all\" when passing query." - added
Input schema / properties / queryAdded value: +{ + "description": "Search the full USGS parameter-code catalog. Case-insensitive; every word must start a word in the parameter name or description (e.g. \"turbidity\", \"nitrate filtered\", \"dissolved oxygen\"). A bare 5-digit code (e.g. \"63680\") returns that code. Omit to list the curated codes.", + "maxLength": 200, + "type": "string" +} - changed
Output schema / anyOfPrevious value: -[ - { - "not": { - "required": [ - "error" - ] - }, - "required": [ - "parameters", - "total" - ] - }, - { - "required": [ - "error" - ] - } -]New value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "parameters", + "total", + "truncated" + ] + }, + { + "required": [ + "error" + ] + } +] - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode."New value: +"Machine-readable failure mode. Declared by this tool: `query_with_group`: query was combined with a group other than \"all\". group filters the curated list only; the USGS catalog carries no matching grouping. `upstream_error`: The USGS parameter-code catalog could not be read — the request failed, timed out, or returned something other than a catalog page. Other values are possible when a failure originates below the handler." - added
Output schema / properties / error / properties / data / properties / reason / examplesAdded value: +[ + "query_with_group", + "upstream_error" +] - added
Output schema / properties / noteAdded value: +{ + "description": "Guidance when a query matched nothing or was truncated. Absent otherwise.", + "type": "string" +} - changed
Output schema / properties / parameters / descriptionPrevious value: -"Matching parameter records with code, name, unit, and group."New value: +"Matching parameter entries. Without query: the curated codes. With query: matching curated codes first (each once, as its curated entry), then catalog records whose name holds every query word, then those matched through the description, each by code — at most 25." - changed
Output schema / properties / parameters / items / descriptionPrevious value: -"A USGS parameter code entry with name, unit, and domain group."New value: +"A USGS parameter code entry." - added
Output schema / properties / parameters / items / properties / descriptionAdded value: +{ + "description": "USGS catalog description, naming the medium, fraction, and method (e.g. \"Turbidity, water, unfiltered, … formazin nephelometric units (FNU)\"). Present on usgs-catalog entries only.", + "type": "string" +} - changed
Output schema / properties / parameters / items / properties / group / descriptionPrevious value: -"Thematic domain grouping for filtering."New value: +"Thematic domain of a curated entry. Absent on usgs-catalog entries." - added
Output schema / properties / parameters / items / properties / sourceAdded value: +{ + "description": "\"curated\": one of the well-known codes this server lists by default. \"usgs-catalog\": a record from the full USGS parameter-code catalog.", + "enum": [ + "curated", + "usgs-catalog" + ], + "type": "string" +} - changed
Output schema / properties / parameters / items / properties / unit / descriptionPrevious value: -"Unit of measure (e.g. \"ft³/s\", \"ft\", \"°C\")."New value: +"Unit of measure (e.g. \"ft³/s\", \"ft\", \"°C\", \"FNU\")." - changed
Output schema / properties / parameters / items / requiredPrevious value: -[ - "code", - "name", - "unit", - "group" -]New value: +[ + "code", + "name", + "unit", + "source" +] - added
Output schema / properties / queryAdded value: +{ + "description": "The catalog query searched, trimmed. Absent when listing the curated codes.", + "type": "string" +} - changed
Output schema / properties / total / descriptionPrevious value: -"Number of parameters returned."New value: +"Number of entries that matched, before the 25-entry cap." - added
Output schema / properties / total / maximumAdded value: +9007199254740991 - added
Output schema / properties / total / minimumAdded value: +-9007199254740991 - changed
Output schema / properties / total / typePrevious value: -"number"New value: +"integer" - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when a query matched more than 25 entries and only the first 25 are listed — add words to narrow it.", + "type": "boolean" +}
2 tool updates
- Changed
water_find_sites15 fields changed- changed
Input schema / properties / bbox / descriptionPrevious value: -"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."New value: +"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). One of the four major filters — bbox, stateCd, countyCd, and huc are mutually exclusive with each other, and exactly one must be supplied." - changed
Input schema / properties / canvas_id / descriptionPrevious value: -"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 fresh canvas."New value: +"Canvas ID from a prior call to add this match set as a table on an existing canvas rather than creating a new one. Each distinct filter set gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the match set exceeds the inline cap and DataCanvas is enabled. Omit to start a fresh canvas." - changed
Input schema / properties / countyCd / descriptionPrevious value: -"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 clarity."New value: +"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\"). The 5 digits already encode the state, so the code stands alone. Major filter — supply exactly one of bbox, stateCd, countyCd, huc." - changed
Input schema / properties / huc / descriptionPrevious value: -"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 accepts no other lengths."New value: +"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 accepts no other lengths. Major filter — supply exactly one of bbox, stateCd, countyCd, huc." - added
Input schema / properties / limitAdded value: +{ + "default": 500, + "description": "Maximum sites to return inline, 1–500. Default 500 (the inline cap).", + "maximum": 500, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / offsetAdded value: +{ + "default": 0, + "description": "Number of matching sites to skip before returning results. Page through matches beyond the inline cap by advancing offset by limit. Default 0.", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Input schema / properties / stateCd / descriptionPrevious value: -"2-character US state abbreviation (e.g. \"VA\", \"WA\"). Returns all sites in the state for the given filters."New value: +"2-character US state abbreviation (e.g. \"VA\", \"WA\"). Returns all sites in the state for the given filters. Major filter — supply exactly one of bbox, stateCd, countyCd, huc." - changed
Output schema / properties / canvas_id / descriptionPrevious value: -"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 sites beyond the inline cap."New value: +"Canvas ID for the DataCanvas holding the full, uncapped match set. Present only when the match set exceeded the 500-site cap and DataCanvas is enabled. Pass to water_dataframe_describe then water_dataframe_query to retrieve sites beyond the inline cap." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_sites_found`: No sites match the given geographic and filter criteria. `invalid_request`: NWIS rejected the request. Filter formats are validated against NWIS-accepted patterns before the call, so this surfaces a well-formed value NWIS still refused (an unknown code, or an unsupported filter combination). `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_sites_found`: No sites match the given geographic and filter criteria. `missing_major_filter`: None of bbox, stateCd, countyCd, or huc was supplied. NWIS scopes every site query by exactly one of them; siteType, parameterCd, and hasDataTypeCd only narrow within that scope. `conflicting_major_filters`: More than one of bbox, stateCd, countyCd, and huc was supplied. NWIS accepts exactly one per request. `invalid_request`: NWIS rejected the request. Filter formats are pattern-validated and the major-filter rule is enforced before the call, so this surfaces a well-formed value NWIS still refused — an unknown state, county, HUC, parameter, or site-type code. `upstream_error`: NWIS returned a 5xx error or the request timed out. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the match set, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_sites_found", - "invalid_request", - "upstream_error" -]New value: +[ + "no_sites_found", + "missing_major_filter", + "conflicting_major_filters", + "invalid_request", + "upstream_error", + "canvas_not_found", + "canvas_capacity_exhausted" +] - changed
Output schema / properties / notice / descriptionPrevious value: -"Advisory when results were capped — points to the staged canvas when DataCanvas is enabled, otherwise to narrowing filters, for retrieving all matches."New value: +"Advisory about the returned window: the staged canvas and how to read it, the filters to narrow by, the window actually returned, the valid offset range when the request landed past the end of the match set, or the fact that a supplied canvas_id went unused because nothing was staged." - changed
Output schema / properties / sites / descriptionPrevious value: -"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)."New value: +"The requested window of matching USGS monitoring sites — the slice starting at offset, at most limit long (500 max). upstreamTotal holds the full match count; canvas_id/table_name point to the staged full set when it exceeded the cap and DataCanvas is enabled." - changed
Output schema / properties / total / descriptionPrevious value: -"Number of sites returned inline in this response (at most 500)."New value: +"Number of sites returned inline in this response — at most limit, and 0 when offset is at or past upstreamTotal." - changed
Output schema / properties / truncated / descriptionPrevious value: -"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, parameterCd, or hasDataTypeCd) to retrieve all matches."New value: +"True when matches remain after the returned window (offset + total < upstreamTotal) — false on the last page, and false for a window starting past the end of the match set, where the notice names the valid offset range instead. Advance offset by limit for the next page, narrow filters (add bbox, countyCd, huc, siteType, parameterCd, or hasDataTypeCd), or when canvas_id is present read the staged set with water_dataframe_describe then water_dataframe_query." - changed
Output schema / properties / upstreamTotal / descriptionPrevious value: -"Total number of sites matching the query upstream, before the 500-site cap was applied. Equals total when truncated=false."New value: +"Total number of sites matching the query upstream, before limit/offset windowing. Equals total when the whole match set fits in one window."
- Changed
water_get_series6 fields changed- changed
Input schema / properties / canvas_id / descriptionPrevious value: -"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."New value: +"Canvas ID from a prior call to add this series as a table on an existing canvas rather than creating a new one. Each distinct site, parameter code, series type, and date range gets its own table name, so re-running the identical query replaces its own table while a different query adds another alongside it. Applies only when the series spills to a canvas. Omit to start a fresh canvas." - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. `canvas_not_found`: The supplied canvas_id names a canvas that never existed or has expired. Raised before the NWIS request, so no upstream call is spent on it. `canvas_capacity_exhausted`: canvas_id was omitted and a fresh canvas was needed to stage the series, but this tenant already holds the maximum number of active canvases. Other values are possible when a failure originates below the handler." - changed
Output schema / properties / error / properties / data / properties / reason / examplesPrevious value: -[ - "no_data_for_range", - "invalid_date_range", - "invalid_request", - "upstream_error" -]New value: +[ + "no_data_for_range", + "invalid_date_range", + "invalid_request", + "upstream_error", + "canvas_not_found", + "canvas_capacity_exhausted" +] - changed
Output schema / properties / notice / descriptionPrevious value: -"Advisory when the result was truncated — narrow the date range or enable DataCanvas for full access."New value: +"Advisory about this result: the staged canvas table and how to read it, the advice to narrow the date range when the series was truncated with no canvas available, or the fact that a supplied canvas_id went unused because nothing was staged." - changed
Output schema / properties / truncated / descriptionPrevious value: -"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."New value: +"True when the result exceeds 500 records and only the most recent were returned inline. When canvas_id is present, inspect the staged table with water_dataframe_describe then read the full series with water_dataframe_query; otherwise narrow the date range." - changed
Output schema / properties / values / descriptionPrevious value: -"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)."New value: +"Time-ordered value records, oldest first within the slice. Holds every record when truncated is false; when truncated, the most recent records only — the last 500 without DataCanvas, or the last N that fit the inline preview budget when the full series is staged on a canvas."
5 tool updates
- Changed
water_dataframe_describe1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
- Changed
water_dataframe_query2 fields changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$" - changed
Output schema / properties / error / properties / data / properties / reason / descriptionPrevious value: -"Machine-readable failure mode. Declared by this tool: `canvas_disabled`: DataCanvas is not enabled on this server instance. `canvas_not_found`: The canvas_id does not exist or has expired. `table_not_found`: The SQL names a table that is not staged on this canvas — it may have expired, or was never created. `system_catalog_access`: The SQL reads a database system catalog (information_schema, pg_catalog, sqlite_master, duckdb_*) instead of a staged table. `invalid_sql`: The SQL is not a read-only SELECT, contains disallowed functions, or is syntactically invalid. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `canvas_disabled`: DataCanvas is not enabled on this server instance. `canvas_not_found`: The canvas_id does not exist or has expired. `table_not_found`: The SQL names a table that is not staged on this canvas — it may have expired, or was never created. `system_catalog_access`: The SQL reads a database system catalog (information_schema, pg_catalog, sqlite_master, duckdb_*) instead of a staged table. `invalid_sql`: The SQL is not a read-only SELECT, contains disallowed functions, is syntactically invalid, or failed on the staged data (a cast or conversion the engine could not apply). Other values are possible when a failure originates below the handler."
- Changed
water_find_sites1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
- Changed
water_get_conditions1 field changed- changed
Output schema / properties / historicalContext / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "comparisonBasis": { - "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null.", - "type": "string" - }, - "p05": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." - }, - "p10": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "p25": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." - }, - "p50": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." - }, - "p75": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." - }, - "p95": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "percentileClass": { - "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", - "enum": [ - "record-high", - "above-normal", - "normal", - "below-normal", - "low", - "record-low", - "unknown" - ], - "type": "string" - }, - "percentileLabel": { - "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not.", - "type": "string" - }, - "periodOfRecord": { - "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", - "type": "string" - } - }, - "required": [ - "percentileClass", - "percentileLabel", - "p05", - "p10", - "p25", - "p50", - "p75", - "p95", - "periodOfRecord", - "comparisonBasis" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "comparisonBasis": { + "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null.", + "type": "string" + }, + "p05": { + "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable.", + "type": [ + "number", + "null" + ] + }, + "p10": { + "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p25": { + "description": "25th percentile (lower quartile) in unitCode. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p50": { + "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p75": { + "description": "75th percentile (upper quartile) in unitCode. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "p95": { + "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable.", + "type": [ + "number", + "null" + ] + }, + "percentileClass": { + "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", + "enum": [ + "record-high", + "above-normal", + "normal", + "below-normal", + "low", + "record-low", + "unknown" + ], + "type": "string" + }, + "percentileLabel": { + "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not.", + "type": "string" + }, + "periodOfRecord": { + "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", + "type": "string" + } + }, + "required": [ + "percentileClass", + "percentileLabel", + "p05", + "p10", + "p25", + "p50", + "p75", + "p95", + "periodOfRecord", + "comparisonBasis" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
water_get_series1 field changed- added
Input schema / properties / canvas_id / patternAdded value: +"^[A-Za-z0-9_-]{10}$"
7 tool updates
- Changed
water_dataframe_describe6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "tables", + "canvas_id" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `canvas_disabled`: DataCanvas is not enabled on this server instance. `canvas_not_found`: The canvas_id does not exist or has expired. Other values are possible when a failure originates below the handler.", + "examples": [ + "canvas_disabled", + "canvas_not_found" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "tables", - "canvas_id" -]
- Changed
water_dataframe_query6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "rows", + "row_count", + "truncated" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `canvas_disabled`: DataCanvas is not enabled on this server instance. `canvas_not_found`: The canvas_id does not exist or has expired. `table_not_found`: The SQL names a table that is not staged on this canvas — it may have expired, or was never created. `system_catalog_access`: The SQL reads a database system catalog (information_schema, pg_catalog, sqlite_master, duckdb_*) instead of a staged table. `invalid_sql`: The SQL is not a read-only SELECT, contains disallowed functions, or is syntactically invalid. Other values are possible when a failure originates below the handler.", + "examples": [ + "canvas_disabled", + "canvas_not_found", + "table_not_found", + "system_catalog_access", + "invalid_sql" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "rows", - "row_count", - "truncated" -]
- Changed
water_find_sites6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "sites", + "total", + "truncated", + "upstreamTotal", + "filters" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `no_sites_found`: No sites match the given geographic and filter criteria. `invalid_request`: NWIS rejected the request. Filter formats are validated against NWIS-accepted patterns before the call, so this surfaces a well-formed value NWIS still refused (an unknown code, or an unsupported filter combination). `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler.", + "examples": [ + "no_sites_found", + "invalid_request", + "upstream_error" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "sites", - "total", - "truncated", - "upstreamTotal", - "filters" -]
- Changed
water_get_conditions6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "currentValue", + "currentDateTime", + "qualifiers", + "historicalContext", + "historicalContextStatus" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no IV data — the site may not exist, or may not measure the requested parameter. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS IV or stat endpoint returned a 5xx error or timed out. Other values are possible when a failure originates below the handler.", + "examples": [ + "no_data_for_parameter", + "invalid_request", + "upstream_error" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "currentValue", - "currentDateTime", - "qualifiers", - "historicalContext", - "historicalContextStatus" -]
- Changed
water_get_readings6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "readings", + "total", + "truncated", + "missingSites", + "query" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `no_data_for_parameter`: NWIS returned no time series — the site(s) may not exist, or may not have data for the requested parameter(s) in the requested period. NWIS returns the same empty response for both cases. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler.", + "examples": [ + "no_data_for_parameter", + "invalid_request", + "upstream_error" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "readings", - "total", - "truncated", - "missingSites", - "query" -]
- Changed
water_get_series6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "seriesType", + "values", + "totalRecords", + "truncated", + "query" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode. Declared by this tool: `no_data_for_range`: The site and parameter combination has no data in the requested date range. `invalid_date_range`: endDate is before startDate, or a date passes the YYYY-MM-DD shape check but is not a real calendar date. `invalid_request`: NWIS rejected the request. Input formats are validated against NWIS-accepted patterns before the call, so this surfaces a value that is well-formed but unacceptable upstream. `upstream_error`: NWIS returned a 5xx error or the request timed out. Other values are possible when a failure originates below the handler.", + "examples": [ + "no_data_for_range", + "invalid_date_range", + "invalid_request", + "upstream_error" + ], + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "seriesType", - "values", - "totalRecords", - "truncated", - "query" -]
- Changed
water_list_parameters6 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / additionalPropertiesAdded value: +false - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Output schema / anyOfAdded value: +[ + { + "not": { + "required": [ + "error" + ] + }, + "required": [ + "parameters", + "total" + ] + }, + { + "required": [ + "error" + ] + } +] - added
Output schema / properties / errorAdded value: +{ + "additionalProperties": {}, + "description": "Present when the call failed. Absent on success.", + "properties": { + "code": { + "description": "JSON-RPC error code for this failure.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "reason": { + "description": "Machine-readable failure mode.", + "type": "string" + }, + "recovery": { + "additionalProperties": {}, + "description": "Actionable next step for the caller.", + "properties": { + "hint": { + "type": "string" + } + }, + "required": [ + "hint" + ], + "type": "object" + }, + "retryable": { + "description": "Whether retrying may succeed.", + "type": "boolean" + } + }, + "type": "object" + }, + "message": { + "description": "Human-readable description of what went wrong.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" +} - removed
Output schema / requiredRemoved value: -[ - "parameters", - "total" -]
1 tool update
- Changed
water_dataframe_query3 fields changed- changed
Output schema / properties / row_count / descriptionPrevious value: -"Number of rows returned in the rows array (up to the 10,000-row cap), not the total matched by the query. A query matching more than 10,000 rows is truncated silently, so row_count then equals the returned count and undercounts the true total. To get the true match count run SELECT COUNT(*) with the same filter, or call water_dataframe_describe for the full row count of the staged table; page large results with LIMIT/OFFSET."New value: +"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 undercounts the true total. To get the true match count run SELECT COUNT(*) with the same filter, or call water_dataframe_describe for the full row count of the staged table; page large results with LIMIT/OFFSET." - added
Output schema / properties / truncatedAdded value: +{ + "description": "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 means rows and row_count are the complete result for this query. When true, narrow the query with WHERE, page with LIMIT/OFFSET, or run SELECT COUNT(*) with the same filter for the true total.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "rows", - "row_count" -]New value: +[ + "rows", + "row_count", + "truncated" +]
4 tool updates
- Changed
water_dataframe_describe1 field changed- changed
Input schema / properties / canvas_id / descriptionPrevious value: -"Canvas ID returned by water_get_series. Identifies the canvas to describe."New value: +"Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas to describe."
- Changed
water_dataframe_query3 fields changed- changed
Input schema / properties / canvas_id / descriptionPrevious value: -"Canvas ID returned by water_get_series. Identifies the canvas holding the data."New value: +"Canvas ID returned by water_get_series or water_find_sites. Identifies the canvas holding the data." - changed
Input schema / properties / sql / descriptionPrevious value: -"Read-only SELECT statement. Reference tables by the names returned in water_get_series table_name. Columns available: date_time (VARCHAR), value (VARCHAR), qualifiers (VARCHAR), site_number (VARCHAR), parameter_cd (VARCHAR), unit_code (VARCHAR). Example: SELECT date_time, value FROM water_series_01646500_00060 ORDER BY date_time DESC LIMIT 10"New value: +"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. Example: SELECT date_time, value FROM water_series_01646500_00060 ORDER BY date_time DESC LIMIT 10" - changed
Output schema / properties / row_count / descriptionPrevious value: -"Total rows matched by the query before the 10,000-row cap. When row_count > rows.length, add WHERE or LIMIT clauses to retrieve specific subsets."New value: +"Number of rows returned in the rows array (up to the 10,000-row cap), not the total matched by the query. A query matching more than 10,000 rows is truncated silently, so row_count then equals the returned count and undercounts the true total. To get the true match count run SELECT COUNT(*) with the same filter, or call water_dataframe_describe for the full row count of the staged table; page large results with LIMIT/OFFSET."
- Changed
water_find_sites1 field changed- changed
Output schema / properties / sites / items / properties / hucCd / descriptionPrevious value: -"Hydrologic Unit Code (HUC) of the watershed containing this site. Length varies by the level NWIS assigned the site — 8-digit (HUC8) and 12-digit (HUC12, e.g. \"020700081005\") values are both common, so do not assume a fixed width. Absent when NWIS assigns the site no HUC. Do not pass this value straight back as the huc input filter, which takes 2 or 8 digits only; HUC codes nest, so the first 8 digits are the containing HUC8 subbasin and are what that filter accepts."New value: +"Hydrologic Unit Code of the watershed containing this site; width varies (8-digit HUC8 and 12-digit HUC12, e.g. \"020700081005\", are both common — do not assume a fixed width), and absent when NWIS assigns none. Do not pass it straight back to the huc filter (which takes 2 or 8 digits); HUCs nest, so its first 8 digits are the containing HUC8 that filter accepts."
- Changed
water_get_conditions1 field changed- changed
Output schema / properties / historicalContext / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "comparisonBasis": { - "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null; carried as its own field because schema description text is invisible wherever the raw value is read.", - "type": "string" - }, - "p05": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." - }, - "p10": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "p25": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." - }, - "p50": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." - }, - "p75": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." - }, - "p95": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "percentileClass": { - "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", - "enum": [ - "record-high", - "above-normal", - "normal", - "below-normal", - "low", - "record-low", - "unknown" - ], - "type": "string" - }, - "percentileLabel": { - "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not. Report this alongside percentileClass rather than the class name alone.", - "type": "string" - }, - "periodOfRecord": { - "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", - "type": "string" - } - }, - "required": [ - "percentileClass", - "percentileLabel", - "p05", - "p10", - "p25", - "p50", - "p75", - "p95", - "periodOfRecord", - "comparisonBasis" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "comparisonBasis": { + "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null.", + "type": "string" + }, + "p05": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." + }, + "p10": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "p25": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." + }, + "p50": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." + }, + "p75": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." + }, + "p95": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "percentileClass": { + "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", + "enum": [ + "record-high", + "above-normal", + "normal", + "below-normal", + "low", + "record-low", + "unknown" + ], + "type": "string" + }, + "percentileLabel": { + "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not.", + "type": "string" + }, + "periodOfRecord": { + "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", + "type": "string" + } + }, + "required": [ + "percentileClass", + "percentileLabel", + "p05", + "p10", + "p25", + "p50", + "p75", + "p95", + "periodOfRecord", + "comparisonBasis" + ], + "type": "object" + }, + { + "type": "null" + } +]
1 tool update
- Changed
water_find_sites2 fields changed- changed
Output schema / properties / sites / items / properties / hucCd / descriptionPrevious value: -"Hydrologic Unit Code (HUC) of the watershed containing this site. Length varies by the level NWIS assigned the site — 8-digit (HUC8) and 12-digit (HUC12, e.g. \"020700081005\") values are both common, so do not assume a fixed width. Do not pass this value straight back as the huc input filter, which takes 2 or 8 digits only; HUC codes nest, so the first 8 digits are the containing HUC8 subbasin and are what that filter accepts."New value: +"Hydrologic Unit Code (HUC) of the watershed containing this site. Length varies by the level NWIS assigned the site — 8-digit (HUC8) and 12-digit (HUC12, e.g. \"020700081005\") values are both common, so do not assume a fixed width. Absent when NWIS assigns the site no HUC. Do not pass this value straight back as the huc input filter, which takes 2 or 8 digits only; HUC codes nest, so the first 8 digits are the containing HUC8 subbasin and are what that filter accepts." - changed
Output schema / properties / sites / items / requiredPrevious value: -[ - "siteNumber", - "siteName", - "siteType", - "latitude", - "longitude", - "hucCd" -]New value: +[ + "siteNumber", + "siteName", + "siteType", + "latitude", + "longitude" +]
1 tool update
- Changed
water_find_sites7 fields changed- added
Input schema / properties / canvas_idAdded value: +{ + "description": "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 fresh canvas.", + "type": "string" +} - added
Output schema / properties / canvas_idAdded value: +{ + "description": "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 sites beyond the inline cap.", + "type": "string" +} - changed
Output schema / properties / notice / descriptionPrevious value: -"Advisory when results were capped — add narrowing filters to retrieve all matches."New value: +"Advisory when results were capped — points to the staged canvas when DataCanvas is enabled, otherwise to narrowing filters, for retrieving all matches." - changed
Output schema / properties / sites / descriptionPrevious value: -"Matching USGS monitoring sites (capped at 500; see truncated/upstreamTotal for overflow)."New value: +"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)." - added
Output schema / properties / table_nameAdded value: +{ + "description": "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.", + "type": "string" +} - changed
Output schema / properties / total / descriptionPrevious value: -"Number of sites returned in this response (at most 500)."New value: +"Number of sites returned inline in this response (at most 500)." - changed
Output schema / properties / truncated / descriptionPrevious value: -"True when the upstream result set exceeded the 500-site cap. Narrow filters (add bbox, countyCd, huc, siteType, parameterCd, or hasDataTypeCd) to retrieve all matches."New value: +"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, parameterCd, or hasDataTypeCd) to retrieve all matches."
1 tool update
- Changed
water_find_sites2 fields changed- added
Output schema / properties / filters / properties / countyCdAdded value: +{ + "description": "County FIPS filter applied, if any.", + "type": "string" +} - changed
Output schema / properties / sites / items / properties / altitude / descriptionPrevious value: -"Altitude of the gage datum in feet above sea level (NAVD 88 or NGVD 29). Populated only when siteOutput=\"expanded\"; absent in basic mode."New value: +"Altitude of the gage datum in feet above sea level (NAVD 88 or NGVD 29). Present in both basic and expanded modes when USGS records an altitude for the site."
1 tool update
- Changed
water_get_conditions6 fields changed- added
Input schema / properties / parameterCd / patternAdded value: +"^\\d{5}$" - added
Input schema / properties / site / patternAdded value: +"^\\d{8,15}$" - changed
Output schema / properties / historicalContext / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "p05": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." - }, - "p10": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "p25": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." - }, - "p50": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." - }, - "p75": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." - }, - "p95": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "percentileClass": { - "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", - "enum": [ - "record-high", - "above-normal", - "normal", - "below-normal", - "low", - "record-low", - "unknown" - ], - "type": "string" - }, - "percentileLabel": { - "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not. Report this alongside percentileClass rather than the class name alone.", - "type": "string" - }, - "periodOfRecord": { - "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", - "type": "string" - } - }, - "required": [ - "percentileClass", - "percentileLabel", - "p05", - "p10", - "p25", - "p50", - "p75", - "p95", - "periodOfRecord" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "comparisonBasis": { + "description": "Fixed disclosure that percentileClass ranks an instantaneous reading against approved daily-mean percentiles — a cross-granularity approximation, not a flood-stage or drought determination. Present whenever historicalContext is non-null; carried as its own field because schema description text is invisible wherever the raw value is read.", + "type": "string" + }, + "p05": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." + }, + "p10": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "p25": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." + }, + "p50": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." + }, + "p75": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." + }, + "p95": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "percentileClass": { + "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", + "enum": [ + "record-high", + "above-normal", + "normal", + "below-normal", + "low", + "record-low", + "unknown" + ], + "type": "string" + }, + "percentileLabel": { + "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not. Report this alongside percentileClass rather than the class name alone.", + "type": "string" + }, + "periodOfRecord": { + "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", + "type": "string" + } + }, + "required": [ + "percentileClass", + "percentileLabel", + "p05", + "p10", + "p25", + "p50", + "p75", + "p95", + "periodOfRecord", + "comparisonBasis" + ], + "type": "object" + }, + { + "type": "null" + } +] - changed
Output schema / properties / historicalContext / descriptionPrevious value: -"Historical percentile context. Null when the stat service has no data for this site."New value: +"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." - added
Output schema / properties / historicalContextStatusAdded value: +{ + "description": "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_record': the stat table is empty — a new site, or a record too short to compute percentiles. 'unavailable': the statistics service call failed — a transient upstream error, not a statement about the site's record; retry shortly.", + "enum": [ + "available", + "no_matching_day", + "no_record", + "unavailable" + ], + "type": "string" +} - changed
Output schema / requiredPrevious value: -[ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "currentValue", - "currentDateTime", - "qualifiers", - "historicalContext" -]New value: +[ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "currentValue", + "currentDateTime", + "qualifiers", + "historicalContext", + "historicalContextStatus" +]
4 tool updates
- Changed
water_find_sites9 fields changed- added
Input schema / properties / bbox / patternAdded value: +"^-?\\d+(\\.\\d+)?(,-?\\d+(\\.\\d+)?){3}$" - changed
Input schema / properties / countyCd / descriptionPrevious value: -"FIPS county code(s) as \"SS:CCC\" or comma-separated list (e.g. \"51:013\" for Arlington, VA). Use with stateCd for clarity."New value: +"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 clarity." - added
Input schema / properties / countyCd / patternAdded value: +"^\\d{5}(,\\d{5}){0,19}$" - changed
Input schema / properties / huc / descriptionPrevious value: -"Hydrologic Unit Code (HUC) — 2, 4, 6, or 8 digits (e.g. \"02070010\" for Potomac/Shenandoah). Scopes results to a watershed."New value: +"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 accepts no other lengths." - added
Input schema / properties / huc / patternAdded value: +"^(\\d{2}|\\d{8})$" - changed
Input schema / properties / parameterCd / descriptionPrevious value: -"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."New value: +"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\")." - added
Input schema / properties / parameterCd / patternAdded value: +"^\\d{5}(,\\d{5})*$" - added
Input schema / properties / stateCd / patternAdded value: +"^[A-Za-z]{2}$" - changed
Output schema / properties / sites / items / properties / hucCd / descriptionPrevious value: -"8-digit Hydrologic Unit Code (HUC8) for the watershed containing this site."New value: +"Hydrologic Unit Code (HUC) of the watershed containing this site. Length varies by the level NWIS assigned the site — 8-digit (HUC8) and 12-digit (HUC12, e.g. \"020700081005\") values are both common, so do not assume a fixed width. Do not pass this value straight back as the huc input filter, which takes 2 or 8 digits only; HUC codes nest, so the first 8 digits are the containing HUC8 subbasin and are what that filter accepts."
- Changed
water_get_conditions1 field changed- changed
Output schema / properties / historicalContext / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "p05": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." - }, - "p10": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "p25": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." - }, - "p50": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." - }, - "p75": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." - }, - "p95": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ], - "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." - }, - "percentileClass": { - "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05).", - "enum": [ - "record-high", - "above-normal", - "normal", - "below-normal", - "low", - "record-low", - "unknown" - ], - "type": "string" - }, - "periodOfRecord": { - "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", - "type": "string" - } - }, - "required": [ - "percentileClass", - "p05", - "p10", - "p25", - "p50", - "p75", - "p95", - "periodOfRecord" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "p05": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "5th percentile value in unitCode for this calendar month+day, based on the period of record. Null if that threshold is unavailable." + }, + "p10": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "10th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "p25": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "25th percentile (lower quartile) in unitCode. Null if unavailable." + }, + "p50": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Median (50th percentile) in unitCode for this calendar month+day. Null if unavailable." + }, + "p75": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "75th percentile (upper quartile) in unitCode. Null if unavailable." + }, + "p95": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "95th percentile value in unitCode for this calendar month+day. Null if unavailable." + }, + "percentileClass": { + "description": "Classification relative to the full period-of-record: record-high (≥ p95), above-normal (p75–p95), normal (p25–p75), below-normal (p10–p25), low (p05–p10), record-low (< p05). See percentileLabel for the threshold in plain language.", + "enum": [ + "record-high", + "above-normal", + "normal", + "below-normal", + "low", + "record-low", + "unknown" + ], + "type": "string" + }, + "percentileLabel": { + "description": "Plain-language threshold for percentileClass (e.g. \"25th–75th percentile\"). The record-high and record-low classes mark percentile-of-record extremes (≥ p95 / < p05), not verified all-time records — this field says so where the class name does not. Report this alongside percentileClass rather than the class name alone.", + "type": "string" + }, + "periodOfRecord": { + "description": "Range of years used to compute the percentile statistics (e.g. \"1930–2025\").", + "type": "string" + } + }, + "required": [ + "percentileClass", + "percentileLabel", + "p05", + "p10", + "p25", + "p50", + "p75", + "p95", + "periodOfRecord" + ], + "type": "object" + }, + { + "type": "null" + } +]
- Changed
water_get_readings10 fields changed- added
Input schema / properties / parameterCd / items / patternAdded value: +"^\\d{5}$" - changed
Input schema / properties / period / descriptionPrevious value: -"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)."New value: +"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 series still returns only its 10 most recent records — use water_get_series to retrieve a full series." - added
Input schema / properties / period / patternAdded value: +"^P(?!$)(\\d+Y)?(\\d+M)?(\\d+W)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+(\\.\\d+)?S)?)?$" - added
Input schema / properties / sites / items / patternAdded value: +"^\\d{8,15}$" - added
Output schema / properties / missingSitesAdded value: +{ + "description": "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. Verify these with water_find_sites.", + "items": { + "description": "A requested USGS site number that returned no time series.", + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / readings / items / properties / totalValuesAdded value: +{ + "description": "Number of value records NWIS returned for this site and parameter over the requested period, before the 10-record cap. Equals values.length when nothing was capped.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / properties / readings / items / properties / values / descriptionPrevious value: -"Time-ordered value records for this site and parameter."New value: +"Time-ordered value records for this site and parameter, capped at the most recent 10. Compare with totalValues to see whether the period held more; use water_get_series for the full series." - changed
Output schema / properties / readings / items / requiredPrevious value: -[ - "siteNumber", - "siteName", - "parameterCd", - "parameterName", - "unitCode", - "values" -]New value: +[ + "siteNumber", + "siteName", + "parameterCd", + "parameterName", + "unitCode", + "values", + "totalValues" +] - added
Output schema / properties / truncatedAdded value: +{ + "description": "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.", + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "readings", - "total", - "query" -]New value: +[ + "readings", + "total", + "truncated", + "missingSites", + "query" +]
- Changed
water_get_series3 fields changed- changed
Input schema / properties / parameterCd / descriptionPrevious value: -"5-digit USGS parameter code (e.g. \"00060\" for discharge, \"00065\" for gage height). Use water_list_parameters to discover available codes."New value: +"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." - added
Input schema / properties / parameterCd / patternAdded value: +"^\\d{5}$" - added
Input schema / properties / site / patternAdded value: +"^\\d{8,15}$"
2 tool updates
- Changed
water_find_sites12 fields changed- added
Output schema / properties / noticeAdded value: +{ + "description": "Advisory when results were capped — add narrowing filters to retrieve all matches.", + "type": "string" +} - changed
Output schema / properties / sites / descriptionPrevious value: -"Matching USGS monitoring sites."New value: +"Matching USGS monitoring sites (capped at 500; see truncated/upstreamTotal for overflow)." - added
Output schema / properties / sites / items / properties / altitudeAdded value: +{ + "description": "Altitude of the gage datum in feet above sea level (NAVD 88 or NGVD 29). Populated only when siteOutput=\"expanded\"; absent in basic mode.", + "type": "number" +} - added
Output schema / properties / sites / items / properties / contributingAreaAdded value: +{ + "description": "Contributing drainage area in square miles (may differ from drainageArea for regulated basins). Populated only when siteOutput=\"expanded\"; absent in basic mode.", + "type": "number" +} - removed
Output schema / properties / sites / items / properties / dataTypesRemoved value: -{ - "description": "Available data type codes at this site.", - "items": { - "description": "A data type code available at this site (e.g. \"iv\", \"dv\").", - "type": "string" - }, - "type": "array" -} - added
Output schema / properties / sites / items / properties / drainageAreaAdded value: +{ + "description": "Total drainage area in square miles. Populated only when siteOutput=\"expanded\"; absent in basic mode.", + "type": "number" +} - removed
Output schema / properties / sites / items / properties / parameterCdsRemoved value: -{ - "description": "Parameter codes available at this site. Present when siteOutput=\"expanded\" or when a parameterCd filter was applied; may be empty for basic output.", - "items": { - "description": "A parameter code available at this site (e.g. \"00060\").", - "type": "string" - }, - "type": "array" -} - changed
Output schema / properties / sites / items / requiredPrevious value: -[ - "siteNumber", - "siteName", - "siteType", - "latitude", - "longitude", - "hucCd", - "dataTypes", - "parameterCds" -]New value: +[ + "siteNumber", + "siteName", + "siteType", + "latitude", + "longitude", + "hucCd" +] - changed
Output schema / properties / total / descriptionPrevious value: -"Total number of sites returned in this response."New value: +"Number of sites returned in this response (at most 500)." - added
Output schema / properties / truncatedAdded value: +{ + "description": "True when the upstream result set exceeded the 500-site cap. Narrow filters (add bbox, countyCd, huc, siteType, parameterCd, or hasDataTypeCd) to retrieve all matches.", + "type": "boolean" +} - added
Output schema / properties / upstreamTotalAdded value: +{ + "description": "Total number of sites matching the query upstream, before the 500-site cap was applied. Equals total when truncated=false.", + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +} - changed
Output schema / requiredPrevious value: -[ - "sites", - "total", - "filters" -]New value: +[ + "sites", + "total", + "truncated", + "upstreamTotal", + "filters" +]
- Changed
water_get_series2 fields changed- added
Input schema / properties / endDate / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$" - added
Input schema / properties / startDate / patternAdded value: +"^\\d{4}-\\d{2}-\\d{2}$"
Related MCP Connectors
Real-time water levels and flow rates from USGS stream gauges
USGS Water MCP — wraps USGS National Water Information System (NWIS) REST services (free, no auth)
USGS Water Services (NWIS) MCP.
River levels & flood alerts: USGS gauges. $0.01/query. Register in-session — free testnet funds.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables querying USGS water data including real-time and historical streamflow, gage height, and water temperature from USGS gauges across the United States.3MIT
- AlicenseNot gradedqualityFmaintenanceWraps USGS NWIS REST services to query water data such as streamflow, groundwater levels, and water quality.10 npmMIT
- FlicenseNot gradedqualityDmaintenanceProvides access to real-time water data from the USGS Water Services API, allowing users to fetch instantaneous measurements like stream flow, gage height, temperature, and water quality parameters from thousands of monitoring stations across the US.3-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query live and historical USGS water data — real-time streamflow, gage height, temperature, precipitation, turbidity and other parameters — for individual gauge sites, and to discover which gauges are actively reporting a given parameter across a US state. Runs either as a keyless local stdio server or through a hosted gateway endpoint, with daily-value time series for trends and hydrographs.211 npmMIT
Glama MCP Gateway
Add one secure layer between your agents and this server.