Waterboards MCP Server
Provides read-only access to curated Databricks Gold views (via a Databricks SQL warehouse) over California drinking water open data — including public water systems and their SAFER risk/violation status, source-water chemistry, school lead sampling results, and tap-water sample results — queryable by ZIP code or water system ID. It also exposes dataset discovery through information_schema, and the server itself is deployed as a Databricks App using the Databricks SDK for authentication.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Waterboards MCP ServerAre any water systems near ZIP 90012 failing SAFER?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Waterboards MCP Server
An MCP server that answers California drinking water questions — public water systems and their SAFER risk/violation status, source-water chemistry, and school lead results, by ZIP code or water system ID — from curated Databricks Gold views over State Water Board open data (data.ca.gov). Built with FastMCP and uv; deployed as a Databricks App.
Design decisions and the review of the original plan: req/mcp_design_review.md (local, git-ignored).
Architecture
data.ca.gov drinking water files (+ Census ZCTA centroids)
└─ sql/drinking_water/ Bronze → Silver → Gold (run in a Databricks SQL warehouse)
└─ users.suresh_kandru.v_* views (the contract in this README)
└─ Databricks SQL warehouse
└─ this server (FastMCP, Databricks Apps, /mcp over HTTP)
└─ MCP client / agentThe server is read-only and queries only <catalog>.<schema>.v_* Gold views (plus information_schema for dataset discovery). Every caller-supplied value is bound as a named SQL parameter.
Related MCP server: SWATGenX MCP Server
Data pipeline
scripts/download_drinking_water_data.py downloads the source files. Upload them (plus the Census ZCTA gazetteer as zcta_centroids.txt) to /Volumes/users/suresh_kandru/raw/drinking_water/current/, then run in order:
Script | Layer | Output |
| setup | volume, |
| Bronze | raw all-string tables |
| Silver | typed |
| Gold | the |
| Bronze/Silver/Gold | DDW SDWIS tap-water results and ZIP-to-service-area lookup → |
Silver turns NULL/NaN/N/A into SQL NULL, parses dates, keeps non-detects explicit (is_non_detect, result_numeric NULL, reporting_limit), and drops street addresses. Systems are located by SAFER coordinates, wells by their own coordinates, and schools by the ZIP parsed from their address.
Tools
Tool | Gold view | Purpose |
|
| What datasets are available |
|
| Water systems near a ZIP, optionally only failing/at-risk/violating |
|
| Profile, SAFER status, violations for one |
|
| Latest well chemistry near a ZIP, filter by chemical |
|
| Latest well chemistry for one |
|
| School lead sampling near a ZIP |
|
| Latest tap/treated-water sample results for the systems serving a ZIP |
All return {results, count, offset, has_more, next_offset, source, retrieved_at}, or {error, hint}. Empty results include an actionable hint.
Gold view contract
The views must exist with at least these columns (the server does SELECT * then filters/orders on them):
View | Required columns |
|
|
|
|
|
|
|
|
|
|
|
|
zip + distance_km are precomputed (pairs up to 50 km), so the server filters distance_km <= :radius_km rather than doing geospatial math per request. Keep PII (street addresses, phone numbers) out of Gold.
Run locally
cp .env.example .env # set DATABRICKS_SQL_WAREHOUSE_ID
databricks auth login --host https://<workspace>.cloud.databricks.com
uv sync --group dev
uv run python main.py # stdioLocal auth uses the Databricks SDK's unified auth (CLI profile via DATABRICKS_CONFIG_PROFILE, or DATABRICKS_HOST + DATABRICKS_TOKEN). Connect a client with an entry like:
{
"mcpServers": {
"waterboards": {
"command": "uv",
"args": ["run", "waterboards-mcp"],
"cwd": "/absolute/path/to/this/repo"
}
}
}HTTP mode (what Databricks Apps runs): MCP_TRANSPORT=streamable-http uv run python main.py, then curl localhost:8000/health.
Verify
uv run pytest tests/ -v
uv run ruff check . && uv run ruff format --check .Tests mock the Databricks SDK; no workspace is needed.
Add a tool
One tool per file in src/waterboards_mcp/tools/, exposing register(mcp); add it to register_tools in tools/__init__.py. Use ZipCode/RadiusKm/PaginationParams from models.py for validated inputs and call warehouse.query_gold(...) with static where/order_by SQL and Params for values. Document source, refresh cadence, and result shape in the docstring — the model reads it.
Deploy
See deploy/databricks/README.md.
Evaluate
See eval/README.md and the mcp-eval skill.
License
See LICENSE.
Available Tools
7 toolswaterboards_find_water_systems_near_zipFind public water systems near a ZIP codeARead-onlyIdempotent
Find drinking water systems serving the area near a ZIP code.
Source: Gold view v_water_systems_near_zip (California State Water
Board SDWIS inventory + SAFER risk assessment). Refreshed when the
pipeline reloads the open data; each result has data_as_of. Only
systems with coordinates in the SAFER assessment can be located.
Distance is from the ZIP centroid to the system's location, so a
nearby system does not necessarily serve that ZIP. Ordered nearest
first.
Set flagged_only to return only systems that are Failing, At-Risk,
or have an MCL, E. coli, or treatment-technique violation.
Each result has pws_id, system_name, county, city, federal_type,
total_population, distance_km, safer_status, is_failing,
violation flags/analytes, risk levels, and source_url. Use
pws_id with waterboards_get_water_system for the full profile. SAFER
status is a regulatory screening, not a tap-water safety determination.
| Name | Required | Description | Default |
|---|---|---|---|
| zip_code | Yes | 5-digit US ZIP code, e.g. 94103. | |
| radius_km | No | Search radius in kilometers (1-50). | |
| pagination | No | ||
| flagged_only | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safe-read profile (readOnly, idempotent, non-destructive, openWorld), so the description is free to add genuinely new context: the Gold-view source and refresh cadence, the `data_as_of` freshness field, the coverage limitation that only geocoded SAFER systems can be located, nearest-first ordering, and the disclaimer that SAFER status is regulatory screening rather than a tap-water safety determination. That is a rich disclosure set beyond structured fields.
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-loads purpose, then source/provenance, limitation, distance semantics, the flagged_only option, and result fields. Most sentences earn their place, though the parenthetical provenance line and the full field enumeration are somewhat heavy for a description whose output schema already exists.
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 geo-search tool with an output schema, an agent has everything needed: what it returns, what it cannot return (ungeocoded systems), how distance is computed, ordering, freshness via `data_as_of`, the special-case filter, and where to go next for a full profile. Nothing material is left to inference.
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?
With 50% schema coverage, the description compensates for the undocumented `flagged_only` parameter by spelling out exactly which statuses/violations it selects. It also clarifies that `distance_km` is measured from ZIP centroid to system location, which the schema does not say. It adds no extra syntax for `radius_km` or the pagination object beyond what the schema provides.
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+resource ("Find drinking water systems serving the area near a ZIP code") and its positional scope, and names the downstream sibling by telling the agent to use `pws_id` with waterboards_get_water_system for the full profile. That cross-reference plus the ZIP/area framing separates it from get_tap_water_for_zip and the source-chemistry siblings without either schema being opened.
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 an explicit condition for the `flagged_only` variant (Failing, At-Risk, MCL/E. coli/treatment-technique violation) and routes the agent onward via `pws_id`. It also flags a real usage trap ("a nearby system does not necessarily serve that ZIP"). It stops short of stating when to prefer this over get_tap_water_for_zip, so no explicit exclusion is given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_get_school_lead_near_zipGet school drinking water lead results near a ZIP codeARead-onlyIdempotent
Get lead-in-drinking-water results at K-12 schools near a ZIP code.
Source: Gold view v_school_lead_near_zip (California State Water Board
school lead sampling, 2020 onward; the data on file stops at the
newest last_observed, so say so when results are old). Distance is
between ZIP centroids. Ordered nearest first, then most recent.
exceedances_only returns only samples the state flagged as above the
lead action level.
Each result has school_name, district, school_zip, sample_location,
result_numeric and units (UG/L), reporting_limit, is_non_detect
(result_numeric is null when true), action_level_exceeded,
follow_up_action, last_observed (sample date), pws_id, and
source_url. These are point-in-time samples, not a health
determination.
| Name | Required | Description | Default |
|---|---|---|---|
| zip_code | Yes | 5-digit US ZIP code, e.g. 94103. | |
| radius_km | No | Search radius in kilometers (1-50). | |
| pagination | No | ||
| exceedances_only | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnly/idempotent/openWorld), it discloses the underlying view, the 2020-onward coverage window with the 'data stops at newest last_observed' caveat, that distance is measured between ZIP centroids, the sort order, and that results are point-in-time samples and not a health determination. That is substantial behavioral context the structured fields do not carry.
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 source, ordering, filter, fields, and caveat in a logical order, with no filler. The full field enumeration is partly redundant given an output schema exists, which keeps it short of a 5.
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 read-only geo-filtered list tool it covers source provenance, coverage limits, ordering, the filter flag, key field semantics (units, non-detect nulls), and the health-interpretation caveat. With an output schema present, return values need no further explanation.
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 50%; the description supplies the missing semantics for exceedances_only ('only samples the state flagged as above the lead action level') and clarifies that distance is ZIP-centroid based. It adds real meaning beyond the schema, though it doesn't elaborate on radius_km or pagination behavior.
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 lead-in-drinking-water results at K-12 schools near a ZIP code.' The school-lead-sampling scope is concrete enough that an agent can distinguish it from siblings like get_tap_water_for_zip and get_source_chemistry_near_zip 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?
The description gives clear context (California school lead sampling, 2020 onward, ZIP-centroid distance, nearest-first ordering) and guidance on the exceedances_only flag, but never states when to prefer this tool over the sibling chemistry/tap-water tools or any exclusions. Usage is implied rather than directed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_get_source_chemistry_near_zipGet drinking water source chemistry near a ZIP codeARead-onlyIdempotent
Get latest source-water chemistry for wells near a ZIP code.
Source: Gold view v_source_chemistry_near_zip (DDW/GAMA drinking water
source chemistry, 2020 to present). Refreshed when the pipeline reloads
the open data; each result has last_observed. Ordered nearest well
first, then most recent.
chemical is a case-insensitive name fragment (e.g. 'arsenic',
'nitrate', 'PFOA'); omit it to see every chemical, which is large. Set
detected_only to skip non-detects.
Each result is the latest sample for one well and chemical: pws_id,
system_name, well_id, chemical_name, result_numeric, units,
reporting_limit, is_non_detect, last_observed (sample date),
sample_count, and source_url. When is_non_detect is true,
result_numeric is null and the chemical was not found above
reporting_limit. This is raw source water before treatment, not tap
water, and not a health determination; units vary by chemical (UG/L,
NG/L, MG/L, ...) so never compare across units.
| Name | Required | Description | Default |
|---|---|---|---|
| chemical | No | ||
| zip_code | Yes | 5-digit US ZIP code, e.g. 94103. | |
| radius_km | No | Search radius in kilometers (1-50). | |
| pagination | No | ||
| detected_only | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description layers substantial extra context: the data source and refresh cadence, that results carry `last_observed`, ordering by nearest then most recent, non-detect semantics (result_numeric null), and strong caveats about units varying so results must not be compared across units. This goes well beyond what structured fields provide.
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-loads purpose, then data provenance, then filter semantics, then caveats. Dense but each paragraph earns its place; the caveat block is slightly long but carries real safety information (units, non-detects).
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 fields need not be re-explained, yet the description usefully interprets them (last_observed, is_non_detect behavior). Combined with refresh cadence and unit caveats, coverage is strong; only radius and pagination handling are left implicit.
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 40%, so the description must compensate, and it does for the two most ambiguous params: `chemical` is a case-insensitive name fragment with examples and an omit-to-see-all option, and `detected_only` is explained. It leaves `radius_km` behavior and `pagination` unaddressed, so a minor gap remains.
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 (get latest source-water chemistry) plus resource and scope (wells near a ZIP code), and immediately differentiates from siblings by noting this is raw source water, not tap water. An agent can distinguish it from get_system_source_chemistry (system-scoped) and get_tap_water_for_zip 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 practical selection guidance: omit `chemical` to see everything (with a warning it is large), set `detected_only` to skip non-detects, and clarifies the dataset is raw source water, not treated tap water. It does not, however, name sibling alternatives (e.g. get_tap_water_for_zip, get_system_source_chemistry) explicitly as substitutes, so routing is implied rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_get_system_source_chemistryGet source chemistry for a water systemARead-onlyIdempotent
Get latest source-water chemistry for one water system's wells.
Source: Gold view v_source_chemistry_latest (DDW/GAMA drinking water
source chemistry, 2020 to present). Refreshed when the pipeline reloads
the open data; each result has last_observed. Ordered by chemical,
then well. Get pws_id from waterboards_find_water_systems_near_zip.
chemical is a case-insensitive name fragment; detected_only skips
non-detects.
Each result is the latest sample for one well and chemical: pws_id,
system_name, well_id, chemical_name, result_numeric, units,
reporting_limit, is_non_detect, last_observed (sample date),
sample_count, and source_url. When is_non_detect is true,
result_numeric is null and the chemical was not found above
reporting_limit. This is raw source water before treatment, not tap
water, and not a health determination; units vary by chemical (UG/L,
NG/L, MG/L, ...) so never compare across units.
| Name | Required | Description | Default |
|---|---|---|---|
| pws_id | Yes | California public water system ID, e.g. CA0110005. | |
| chemical | No | ||
| pagination | No | ||
| detected_only | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds substantial context beyond them: the underlying 'Gold view v_source_chemistry_latest' provenance, refresh cadence, result ordering (chemical, then well), non-detect semantics (result_numeric null when is_non_detect is true), and a critical units caveat warning against cross-unit comparison.
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 provenance, then parameters, then result semantics in clearly separated paragraphs. Dense but every sentence carries operational value (units warning, non-detect semantics, source tool); slightly long, 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?
An output schema exists, so return values need not be enumerated, yet the description goes further and explains how to interpret the key fields. Combined with provenance, ordering, the non-detect rule, and the pre-treatment/units caveats, an agent has everything needed to call and correctly interpret this tool.
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 only 25%, but the description compensates for the two non-obvious parameters: 'chemical' is clarified as a case-insensitive name fragment, and 'detected_only' is defined as skipping non-detects. It also explains where pws_id comes from, though it says nothing about the pagination limits/offset beyond what the schema documents.
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?
Opens with a specific verb+resource+scope: 'Get latest source-water chemistry for one water system's wells.' That scope ('one water system') cleanly separates it from the sibling waterboards_get_source_chemistry_near_zip and from waterboards_get_tap_water_for_zip, which the description also explicitly contrasts against.
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 routes the agent to waterboards_find_water_systems_near_zip to obtain the required pws_id, and states when the result is not appropriate ('not tap water, and not a health determination'), which tells the agent to reach for the tap-water sibling instead in those cases.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_get_tap_water_for_zipGet tap water sample results for a California ZIP codeARead-onlyIdempotent
Get the latest tap-water test results for a California ZIP code.
Source: Gold view v_tap_water_for_zip (DDW SDWIS sample results, 2023 to
present, joined to water-system service-area boundaries). Refreshed when
the pipeline reloads the open data; each result has last_observed.
Systems are ordered by how much of the ZIP they serve
(pct_of_zip_covered, highest first); a ZIP can be served by several
systems, so use the largest coverage as the likely supplier.
stage is 'distribution' (samples from the pipes that reach customers,
the closest to tap water; default) or 'treated' (leaving the treatment
plant). chemical is a case-insensitive name fragment (e.g. 'lead',
'arsenic', 'pfas', 'nitrate'). detected_only (default true) hides
non-detects; exceeds_mcl_only shows only results above the legal
limit. min_coverage_pct (default 10) hides systems covering less of
the ZIP than that; lower it to 0 to see every overlapping system.
Each result: zip, pct_of_zip_covered, pws_id, system_name, total_population, water_stage, analyte_name, result_numeric, units, is_non_detect, reporting_limit, mcl, exceeds_mcl, any_exceeds_mcl, max_result_numeric, sample_count, last_observed, sampling_point, method, source_url. When is_non_detect is true, result_numeric is null and the analyte was below reporting_limit.
These are system-level samples, not results for an individual address or home. A single sample above the MCL is not a violation by itself (many limits apply to running averages), and this is not a health determination. Units vary by analyte; never compare across units.
| Name | Required | Description | Default |
|---|---|---|---|
| stage | No | distribution | |
| chemical | No | ||
| zip_code | Yes | 5-digit US ZIP code, e.g. 94103. | |
| pagination | No | ||
| detected_only | No | ||
| exceeds_mcl_only | No | ||
| min_coverage_pct | No | Minimum % of the ZIP a water system must cover. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: names the underlying view, refresh cadence and last_observed, ordering rationale, the non-detect convention (result_numeric null below reporting_limit), and important caveats that a single MCL exceedance is not a violation and this is not a health determination. Annotation coverage is strong but the description adds genuinely non-obvious behavioral context.
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 data source, ordering, parameters, then caveats – a logical order. It is dense and long, and the full enumeration of return fields is partly redundant given an output schema exists, though it earns its place by explaining is_non_detect semantics.
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 7-parameter read tool with an output schema and full annotations, the description supplies everything an agent needs: scope, source, ordering, per-filter behavior, and interpretation limits. Nothing required 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 coverage is only 29%, so the description must compensate, and it does: it defines stage values, gives chemical fragment examples ('lead', 'arsenic', 'pfas', 'nitrate'), and explains detected_only (hides non-detects), exceeds_mcl_only (above legal limit), and min_coverage_pct (default 10, lower to 0 for all overlapping systems). Only zip_code and pagination are left to the schema, which documents them.
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 the latest tap-water test results for a California ZIP code') and pins the domain to California and to system-level tap-water samples. The stage distinction (distribution = pipes reaching customers vs treated = leaving the plant) makes it distinguishable from source-chemistry and school-lead siblings.
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 operating context: defaults for stage, detected_only, exceeds_mcl_only and min_coverage_pct, plus guidance to use the largest pct_of_zip_covered as the likely supplier. It does not explicitly name when to prefer a sibling tool (e.g. get_source_chemistry_near_zip) over this one, so routing is implied rather than spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_get_water_systemGet a public water system profileARead-onlyIdempotent
Get the profile and compliance/risk summary of one water system.
Source: Gold view v_water_systems (California State Water Board SDWIS
inventory + SAFER risk assessment). Refreshed when the pipeline reloads
the open data; the result carries data_as_of.
The single result has system name, county, city, type, owner, source
type, population, service connections, last sanitary survey,
safer_status, is_failing, economic_status (DAC/SDAC), violation
flags with analytes (primary/secondary MCL, E. coli, treatment
technique, monitoring), risk levels by category, and source_url.
has_risk_assessment is false when the system is not in the SAFER
file, in which case the violation/risk fields are null (unknown, not
"none"). This is a regulatory screening, not a tap-water safety
determination.
| Name | Required | Description | Default |
|---|---|---|---|
| pws_id | Yes | California public water system ID, e.g. CA0110005. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety (readOnly/idempotent/openWorld), and the description goes well beyond them: it discloses the data source and view, that results carry `data_as_of` and refresh only on pipeline reload, that `has_risk_assessment: false` means fields are null-by-ignorance rather than 'none', and explicitly bounds interpretation ('regulatory screening, not a tap-water safety determination'). That is unusually rich behavioral context.
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 the one-line purpose, then provenance, then return contents, then the null-semantics caveat and the screening disclaimer. The enumerated field list is long but it is bounded and each element is informative; nothing reads as 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?
Despite an output schema existing, the description supplies the semantics the schema cannot: provenance/freshness, the null-vs-none distinction, the meaning of has_risk_assessment, and an explicit interpretive caveat. An agent can call and correctly interpret this tool with no further context.
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?
One parameter with 100% schema coverage, including a regex pattern and an example. The description adds nothing about pws_id format or origin, so the schema does all the work; 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 (get), resource (profile/compliance/risk of ONE water system), and scope. The 'one water system' framing distinguishes it cleanly from sibling list/near-zip tools, and 'compliance/risk summary' separates it from the chemistry-focused siblings.
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?
Usage is implied by the single-system framing and the required pws_id, but the description never says when to pick this over waterboards_get_system_source_chemistry or the near-zip finders, nor does it state a prerequisite beyond having the ID. Adequate context, no explicit routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
waterboards_list_datasetsList waterboard datasetsARead-onlyIdempotent
List the water datasets (Gold views) available through this server.
Source: Unity Catalog information_schema for the configured Gold schema. Refresh cadence: reflects the current catalog; underlying data refresh varies by dataset (see each view's comment).
Returns a pagination envelope; each result has table_name and
table_comment.
| Name | Required | Description | Default |
|---|---|---|---|
| pagination | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint, so the safety profile is covered. The description adds genuinely useful context beyond them: the data source (Unity Catalog information_schema for the configured Gold schema), a refresh-cadence caveat that underlying data varies per view, and the shape of the return. It does not state auth/permission requirements, which keeps it short of a 5.
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?
Four short, labelled sentences, each earning its place: what it lists, where the data comes from, a freshness caveat, and the return shape. Front-loaded with the core action.
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 and rich read-only annotations, the description does not need to explain return values, yet it helpfully summarises them. What remains unaddressed is whether the listing is scoped/filtered by catalog permissions or returns everything in the Gold schema.
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?
Reported schema description coverage is 0%, so the description nominally carries the burden, and it only mentions the pagination envelope on the output side, not the limit/offset inputs. The nested schema does in fact document limit (1-100) and offset, so the agent is not fully in the dark, but the description adds no input-level semantics of its own.
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 (List) plus a specific resource (water datasets / Gold views) and scopes it to this server. It reads as a metadata-discovery tool, which cleanly separates it from siblings like waterboards_get_water_system or waterboards_get_source_chemistry_near_zip that return actual data.
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?
The phrase 'available through this server' implies this is a discovery/first-step tool, but there is no explicit 'use this before calling X' or 'do not use this to retrieve measurements' guidance. The when-to-use is inferable rather than stated, so a 3 is warranted.
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.
7 tool updates
v0.1.0- First observed
waterboards_find_water_systems_near_zip - First observed
waterboards_get_school_lead_near_zip - First observed
waterboards_get_source_chemistry_near_zip - First observed
waterboards_get_system_source_chemistry - First observed
waterboards_get_tap_water_for_zip - First observed
waterboards_get_water_system - First observed
waterboards_list_datasets
TDQS
Scored across 7 tools
Tools are largely distinct by resource and input key (ZIP vs pws_id), so most selections are clear. The two source-chemistry tools could be briefly confused because they share the same output schema, but the descriptions and name qualifiers ('near_zip' vs 'system') clarify scope.
All names use a consistent waterboards_ prefix and snake_case verb_noun pattern. Verb choices (list/find/get) are appropriate and no mixed conventions appear.
7 tools is well-scoped for a read-only water-data server. Each tool covers a clear data domain or lookup mode, with no redundant or filler tools.
The surface covers dataset discovery, water-system lookup, source chemistry by geography and system, school lead, and ZIP tap water. Minor gaps remain, such as direct tap-water or school-lead lookup by pws_id/system and broader search by county or system name, but core workflows are supported.
Maintenance
Related MCP Connectors
Query US tap-water quality from the EPA's SDWIS records — by city, system, or contaminant.
Query real-time and historical USGS water data from ~8,000 stream gages and groundwater wells.
Public hazard, water, weather and EPA data with source, age and licence on every reading.
Your Databricks Lakehouse in natural language: run SQL on your SQL warehouses, track long-running qu
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

SWATGenX MCP Serverofficial
AlicenseNot gradedqualityCmaintenanceEnables search and retrieval of calibrated SWAT+ watershed models, national groundwater lithology and PFAS inventories, plus API-key-secured ordering and downloading of custom watershed models.1MIT- AlicenseNot gradedqualityBmaintenanceProvides governed Text2SQL over PostgreSQL with AST-guarded SQL validation, read-only transactions, and allowlisted analysis tools, enabling safe natural-language data querying and reproducible evaluation via MCP.MIT
- AlicenseNot gradedqualityCmaintenanceEnables querying, sampling, and exporting datasets from over twenty keyless free public data sources spanning the EU, Switzerland, Germany, the US, and global providers through one uniform set of tools. A single consistent interface lets users list databases and tables, inspect schemas and query guides, run queries, and export results as CSV or JSON without per-API clients.Apache 2.0