# io.github.cyanheads/epa-mcp-server (remote · epa.caseyjhand.com)

Search EPA compliance, TRI, Superfund, drinking-water, EJScreen, and AirNow data.

- Trust score: 75/100 (medium)
- Change this week: +3
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- remote · `epa.caseyjhand.com`: 75/100 (this document), [markdown](https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa.md), [page](https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa)
- npm · `@cyanheads/epa-mcp-server`: 39/100, [markdown](https://verifymcp.io/servers/cyanheads-epa-mcp-server/cyanheads-epa-mcp-server.md), [page](https://verifymcp.io/servers/cyanheads-epa-mcp-server/cyanheads-epa-mcp-server)

## Channel facts

- Endpoint: `https://epa.caseyjhand.com/mcp`
- Transports: `streamable-http`
- Auth: `none`
- Version: `0.8.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Endpoint Security**: 83/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - No authorisation is required to call this server. Every tool declares its destructiveHint and none is destructive, so open access doesn't expose one.
  - HTTPS is enforced; there's no plaintext access path.
  - The HSTS (Strict-Transport-Security) header is present.
  - DNSSEC is configured correctly; the domain's records validate against the full chain to the root.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 74/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 5573 tokens (~464/item across 12 items; 11 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 0/100
  - Stability check failed: schema churn in the 27 days we've observed: 8 tool removals, 0 breaking changes, 0 auth/transport breaks, 11 additions.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 11 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 13 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the io.github.cyanheads/epa-mcp-server server?

io.github.cyanheads/epa-mcp-server is a hosted endpoint at https://epa.caseyjhand.com/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add --transport http cyanheads-epa-mcp-server 'https://epa.caseyjhand.com/mcp'
```

### Cursor

```json
{
  "mcpServers": {
    "cyanheads-epa-mcp-server": {
      "url": "https://epa.caseyjhand.com/mcp"
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "cyanheads-epa-mcp-server": {
      "type": "http",
      "url": "https://epa.caseyjhand.com/mcp"
    }
  }
}
```

### Codex

```toml
[mcp_servers.cyanheads-epa-mcp-server]
url = "https://epa.caseyjhand.com/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "cyanheads-epa-mcp-server": {
      "type": "remote",
      "url": "https://epa.caseyjhand.com/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add cyanheads-epa-mcp-server --url 'https://epa.caseyjhand.com/mcp' --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  cyanheads-epa-mcp-server:
    url: "https://epa.caseyjhand.com/mcp"
```

### Netclaw

```json
{
  "McpServers": {
    "cyanheads-epa-mcp-server": {
      "Transport": "http",
      "Url": "https://epa.caseyjhand.com/mcp"
    }
  }
}
```

### Vellum

```bash
assistant mcp add cyanheads-epa-mcp-server -t streamable-http -u 'https://epa.caseyjhand.com/mcp'
```

### Other

```json
{
  "mcpServers": {
    "cyanheads-epa-mcp-server": {
      "type": "http",
      "url": "https://epa.caseyjhand.com/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-01 (score 75, +3)

- [security] The server rewrote its instructions, which are the text every model session reads
- [security] Tool “epa_echo_search_facilities” rewrote its description, which is the text the model reads
- [security] Tool “epa_echo_search_violations” rewrote its description, which is the text the model reads
- [security] Tool “epa_ejscreen_get_scores” rewrote its description, which is the text the model reads
- [security] Tool “epa_tri_get_facility_releases” rewrote its description, which is the text the model reads
- [security] Tool “epa_tri_search_releases” rewrote its description, which is the text the model reads
- [security] Tool “epa_water_search_systems” rewrote its description, which is the text the model reads
- [security] Tool “epa_echo_get_facility” rewrote its description, which is the text the model reads
- [security] Tool “epa_superfund_search_sites” rewrote its description, which is the text the model reads
- [functional regression] Schema quality: 355 → 464
- [functional improvement] Schema quality: 355 → 310
- [functional improvement] Schema quality: unverified → 100
- [functional] New resource “epa-guide”
- [functional] Server version: 0.7.0 → 0.8.0
- [functional] Server version: 0.6.0 → 0.7.0
- [functional] New tool “epa_echo_get_case”
- [functional] New tool “epa_superfund_get_site”
- [cosmetic] “epa_echo_get_facility” added an optional parameter “drinking_water_violations_offset”
- [cosmetic] “epa_echo_get_facility” added an optional parameter “inspections_limit”
- [cosmetic] “epa_echo_get_facility” added an optional parameter “inspections_offset”
- [cosmetic] “epa_echo_search_facilities” added an optional parameter “county”
- [cosmetic] “epa_echo_search_facilities” added an optional parameter “county_fips”
- [cosmetic] “epa_echo_search_facilities” added an optional parameter “facility_name”
- [cosmetic] “epa_echo_search_facilities” added an optional parameter “naics”
- [cosmetic] “epa_echo_search_facilities” added an optional parameter “offset”
- [cosmetic] “epa_echo_search_violations” added an optional parameter “offset”
- [cosmetic] “epa_superfund_search_sites” added an optional parameter “offset”
- [cosmetic] “epa_tri_get_facility_releases” added an optional parameter “limit”
- [cosmetic] “epa_tri_get_facility_releases” added an optional parameter “offset”
- [cosmetic] “epa_tri_search_releases” added an optional parameter “offset”
- [cosmetic] “epa_water_search_systems” added an optional parameter “active_only”
- [cosmetic] “epa_water_search_systems” added an optional parameter “county”
- [cosmetic] “epa_water_search_systems” added an optional parameter “offset”
- [cosmetic] “epa_echo_get_facility” added an optional parameter “drinking_water_violations_limit”
- [cosmetic] “epa_superfund_search_sites” added an optional parameter “exclude_archived”
- [cosmetic] “epa_echo_search_facilities” reworded the description of “limit”
- [cosmetic] “epa_echo_search_facilities” reworded the description of “state”
- [cosmetic] “epa_echo_search_facilities” reworded the description of “zip_code”
- [cosmetic] “epa_echo_search_violations” reworded the description of “limit”
- [cosmetic] “epa_echo_search_violations” reworded the description of “program”
- [cosmetic] “epa_echo_search_violations” reworded the description of “state”
- [cosmetic] “epa_echo_search_violations” reworded the description of “zip_code”
- [cosmetic] “epa_superfund_search_sites” reworded the description of “limit”
- [cosmetic] “epa_superfund_search_sites” reworded the description of “state”
- [cosmetic] “epa_superfund_search_sites” reworded the description of “zip_code”
- [cosmetic] “epa_tri_get_facility_releases” reworded the description of “year”
- [cosmetic] “epa_tri_search_releases” reworded the description of “county”
- [cosmetic] “epa_tri_search_releases” reworded the description of “include_release_breakdown”
- [cosmetic] “epa_tri_search_releases” reworded the description of “limit”
- [cosmetic] “epa_tri_search_releases” reworded the description of “state”
- [cosmetic] “epa_tri_search_releases” reworded the description of “year”
- [cosmetic] “epa_water_search_systems” reworded the description of “limit”
- [cosmetic] “epa_water_search_systems” reworded the description of “has_violation”
- [cosmetic] “epa_water_search_systems” reworded the description of “state”
- [cosmetic] “epa_water_search_systems” reworded the description of “zip_code”
- [cosmetic] “epa_echo_get_facility” reworded the description of “registry_id”
- [cosmetic] “epa_airnow_get_conditions” reworded the description of “zip_code”
- [cosmetic] “epa_superfund_search_sites” reworded the description of “npl_status”

### 2026-10-24 (score 72, −7)

- [security regression] Stability: 0.47 → fail
- [security regression] Tool “epa_search_water_systems” was removed
- [security regression] Tool “epa_search_violations” was removed
- [security regression] Tool “epa_search_tri_releases” was removed
- [security regression] Tool “epa_search_facilities” was removed
- [security regression] Tool “epa_get_tri_releases” was removed
- [security regression] Tool “epa_get_facility” was removed
- [security regression] Tool “epa_get_ejscreen” was removed
- [security regression] Tool “epa_get_air_quality” was removed
- [security regression] Tool “epa_search_superfund” was removed
- [security] The server rewrote its instructions, which are the text every model session reads
- [functional] Server version: 0.5.0 → 0.6.0
- [functional] New tool “epa_airnow_get_conditions”
- [functional] New tool “epa_water_search_systems”
- [functional] New tool “epa_tri_search_releases”
- [functional] New tool “epa_tri_get_facility_releases”
- [functional] New tool “epa_superfund_search_sites”
- [functional] New tool “epa_ejscreen_get_scores”
- [functional] New tool “epa_echo_search_violations”
- [functional] New tool “epa_echo_search_facilities”
- [functional] New tool “epa_echo_get_facility”

### 2026-10-23 (score 79, +1)

No change was recorded against any check on this day. Stability & Change Management went from 43 to 47. That category is still filling its 30-day observation window: 13 days of observed history at the previous scan, 14 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-20 (score 78, +1)

No change was recorded against any check on this day. Stability & Change Management went from 33 to 37. That category is still filling its 30-day observation window: 10 days of observed history at the previous scan, 11 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-19 (score 77, 0)

- [functional] Server version: 0.4.1 → 0.5.0
- [cosmetic] “epa_get_tri_releases” reworded the description of “year”

### 2026-10-18 (score 77, +1)

No change was recorded against any check on this day. Stability & Change Management went from 27 to 30. That category is still filling its 30-day observation window: 8 days of observed history at the previous scan, 9 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-17 (score 76, +52)

- [security improvement] Injection markers: unverified → pass
- [security improvement] Authorization: unverified → partial
- [security improvement] HSTS header: fail → pass
- [security improvement] Transport: fail → pass
- [security] The server rewrote its instructions, which are the text every model session reads
- [security] Tool “epa_search_tri_releases” rewrote its description, which is the text the model reads
- [security] Tool “epa_search_violations” rewrote its description, which is the text the model reads
- [security] Tool “epa_get_facility” rewrote its description, which is the text the model reads
- [security] Tool “epa_get_tri_releases” rewrote its description, which is the text the model reads
- [security] Tool “epa_search_facilities” rewrote its description, which is the text the model reads
- [functional regression] Schema quality: 306 → 343
- [functional improvement] Endpoint reachability: not serving MCP → reachable
- [functional improvement] Stability: unverified → 0.27
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] MCP protocol: unverified → pass
- [functional] Server version: 0.4.0 → 0.4.1
- [cosmetic] “epa_search_facilities” reworded the description of “active_only”
- [cosmetic] “epa_search_facilities” reworded the description of “city”
- [cosmetic] “epa_search_facilities” reworded the description of “has_violation”
- [cosmetic] “epa_search_facilities” reworded the description of “programs”
- [cosmetic] “epa_search_superfund” reworded the description of “city”
- [cosmetic] “epa_search_tri_releases” reworded the description of “chemical_name”
- [cosmetic] “epa_search_tri_releases” reworded the description of “county”
- [cosmetic] “epa_search_tri_releases” reworded the description of “year”
- [cosmetic] “epa_search_violations” reworded the description of “case_type”
- [cosmetic] “epa_search_violations” reworded the description of “date_filed_end”
- [cosmetic] “epa_search_violations” reworded the description of “date_filed_start”
- [cosmetic] “epa_search_violations” reworded the description of “program”
- [cosmetic] “epa_search_violations” reworded the description of “zip_code”
- [cosmetic] “epa_get_tri_releases” reworded the description of “chemical_name”
- [cosmetic] “epa_get_tri_releases” reworded the description of “facility_id”

### 2026-10-16 (score 24, −51)

- [security regression] Endpoint reachability: reachable → not serving MCP
- [security regression] Authorization: partial → unverified
- [security regression] Tool safety: pass → unverified
- [security regression] Stability: 0.20 → unverified
- [security regression] Transport: pass → fail
- [security regression] HSTS header: pass → fail
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] First check of Schema quality: unverified

## MCP tools (11)

### `epa_echo_search_facilities` (~872 tokens)

Search EPA Facilities

ECHO: Search EPA-regulated facilities by location, county, facility name, NAICS industry code, environmental program, or compliance status across CAA, CWA, RCRA, TRI, and SDWA. Returns facility name, EPA Registry ID, coordinates, county FIPS code, per-program registration flags, TRI facility IDs, five-year inspection and penalty totals, and TRI release totals. Registry IDs returned here feed epa_echo_get_facility; TRI facility IDs feed epa_tri_get_facility_releases. Supports proximity search via latitude + longitude + radius_miles. At least one geographic filter (zip_code, state, city, county_fips, or a complete latitude+longitude+radius_miles triple) is required — unscoped searches time out. Page past limit with offset and nextOffset.

Input parameters:

- `active_only` (boolean): When true, return only facilities with an active permit
- `city` (string): City name, case-insensitive; matches cities that begin with it ("TACOMA", or "SEAT" for SEATTLE and SEATAC). Pair with state.
- `county` (string): County name, case-insensitive. A trailing County, Parish, Borough, City and Borough, Census Area, or Municipality is dropped ("King County" searches KING), then it matches recorded county names that…
- `county_fips` (string): 5-digit county FIPS code, state + county (e.g. "53033" for King County, WA); matches it exactly and scopes a search on its own. Leaves out registrations with no program record, which county includes.
- `facility_name` (string): Facility name words, case-insensitive: every word must appear as a whole word, in any order, in a name ECHO records for the facility, so "BOEING" matches BOEING PLANT 2 and "OEING" matches nothing. A…
- `has_violation` (boolean): When true, return only facilities with at least one program in significant noncompliance (complianceStatus "Significant Violation")
- `latitude` (number): Latitude in decimal degrees. Pair with longitude and radius_miles for proximity search.
- `limit` (integer): Maximum number of facilities to return in one call (1–100, default 50). A larger value, up to 5,000, returns 100 with a notice. Page past it with offset and nextOffset.
- `longitude` (number): Longitude in decimal degrees. Pair with latitude and radius_miles for proximity search.
- `naics` (string): NAICS industry code, 2–6 digits; fewer than six match every code that begins with them ("3364" for aerospace product and parts manufacturing). Pair with a geographic filter.
- `offset` (integer): Number of facilities to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `programs` (array): Restrict results to facilities registered in one environmental program — ECHO filters on a single program per search, so pass one value. CAA=air, CWA=water, RCRA=hazardous waste, TRI=toxic releases,…
- `radius_miles` (number): Search radius in miles around the given latitude/longitude (max 100). Requires latitude and longitude.
- `state` (string): 2-letter state or territory code (e.g. "WA", "PR"), case-insensitive. Required with county. ECHO also files facilities under codes outside the USPS set, such as DM for offshore Gulf of Mexico platfor…
- `zip_code` (string): 5-digit ZIP code to search within; a ZIP+4 such as "98108-1234" searches its first five digits

Output parameters:

- `effectiveQuery` (string): The search as the server sent it upstream, in this tool's input names: normalized values, how each text filter matched ("=" exact, "contains", "begins with", "has words"), and defaults that change wh…
- `error` (object): Present when the call failed. Absent on success.
- `facilities` (array): One page of matching EPA facilities, in ECHO's order: limit facilities from offset
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when the page is empty: no facility matched, or offset is past the last match. Absent when facilities are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Present when more facilities follow: names the offset that continues the list.
- `totalCount` (number): Facilities matched before limit and offset were applied

### `epa_echo_get_facility` (~503 tokens)

Get EPA Facility Compliance Profile

ECHO: Retrieve a full compliance profile for a single EPA-regulated facility by EPA Registry ID or EPA program ID. Returns facility metadata, the NAICS industry codes its programs record, ECHO's facility-level compliance status, a per-statute (CAA, CWA, RCRA, SDWA, and others ECHO tracks) compliance summary, inspections and other compliance monitoring records from the last five years (newest first, paged), five years of formal enforcement actions with penalties, five years of drinking-water violations with contaminant, measure, MCL, status, and enforcement actions (paged), the latest TRI release total, and the facility's TRI facility IDs for epa_tri_get_facility_releases. Use epa_echo_search_facilities to discover Registry IDs; pass a public water system's PWSID from epa_water_search_systems to get its drinking-water violations. For area-level enforcement discovery use epa_echo_search_violations instead.

Input parameters:

- `drinking_water_violations_limit` (integer): Maximum number of drinking-water violations to return in one call (1–200, default 50). A larger value, up to 5,000, returns 200 with a notice. Page past it with drinking_water_violations_offset and d…
- `drinking_water_violations_offset` (integer): Number of drinking-water violations to skip before this page (default 0). Pass the previous call's drinkingWaterViolationsNextOffset to continue; a value past the end returns an empty page.
- `inspections_limit` (integer): Maximum number of inspections to return in one call (1–100, default 100). A larger value, up to 5,000, returns 100 with a notice. Page past it with inspections_offset and inspectionsNextOffset.
- `inspections_offset` (integer): Number of inspections to skip before this page (default 0). Pass the previous call's inspectionsNextOffset to continue; a value past the end returns an empty page.
- `registry_id` (string, required): EPA FRS Registry ID for the facility (from epa_echo_search_facilities), or an EPA program ID ECHO resolves to one: a public water system PWSID (from epa_water_search_systems, e.g. WA5300484), a TRI f…

Output parameters:

- `airCompliance` (object): Clean Air Act status. Present only when programs.air is true and ECHO reports a CAA status.
- `city` (string): City
- `compliance` (object): Facility-level noncompliance count from ECHO's facility search record. Absent when ECHO reports none or the facility search request fails.
- `complianceStatus` (string): ECHO's facility-level compliance status, the same value epa_echo_search_facilities returns (e.g. "Significant Violation"). Absent when ECHO assigns none, as for TRI-only facilities, or when ECHO's fa…
- `county` (string): County name
- `drinkingWaterViolations` (array): Drinking-water (SDWIS) violations in ECHO's 5-year report for a public water system, newest compliance period first: one page of drinking_water_violations_limit entries from drinking_water_violations…
- `drinkingWaterViolationsNextOffset` (number): Offset of the next page of drinkingWaterViolations: present exactly when more follow, absent on the last page. Pass it as drinking_water_violations_offset.
- `drinkingWaterViolationsTotal` (number): Drinking-water violations in ECHO's report for this facility, across every page; 0 when it has no SDWIS record
- `error` (object): Present when the call failed. Absent on success.
- `fipsCode` (string): 5-digit county FIPS code
- `formalActions` (array): Formal enforcement actions over the 5-year window, newest first
- `inspections` (array): Inspections, evaluations, investigations, and other compliance monitoring records over the 5-year window, newest first: one page of inspections_limit entries from inspections_offset.
- `inspectionsNextOffset` (number): Offset of the next page of inspections: present exactly when more follow, absent on the last page. Pass it as inspections_offset.
- `inspectionsTotal` (number): Compliance monitoring records in ECHO's 5-year history for this facility, across every page
- `latitude` (number): Latitude in decimal degrees
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `longitude` (number): Longitude in decimal degrees
- `naics` (array): NAICS industry codes the facility's program records carry, each code once, ascending by code. Programs can record different codes for one site. Empty when no program records one.
- `name` (string): Facility name
- `notice` (string): Present when ECHO's facility search request failed, so complianceStatus and compliance are missing; every other field comes from the facility's detailed report.
- `programCompliance` (array): ECHO's per-statute enforcement and compliance summary. Empty when the facility is in no compliance-tracked program, as for TRI-only facilities.
- `programs` (object): Program registration flags, from the ECHO systems in the facility's detailed report
- `registryId` (string): EPA FRS Registry ID
- `state` (string): 2-letter state abbreviation
- `street` (string): Street address
- `triFacilityIds` (array): 15-character TRI facility IDs recorded for this facility, most recently reported first — pass one as facility_id to epa_tri_get_facility_releases for per-chemical releases. A site can carry several f…
- `triReleasesTransfersInLbs` (number): Total TRI on- and off-site releases and transfers in pounds for triReleasesTransfersYear, ECHO's latest TRI year for this facility. Absent when ECHO holds no TRI history for it.
- `triReleasesTransfersYear` (number): TRI reporting year triReleasesTransfersInLbs covers
- `truncated` (boolean): True when a list was cut at its limit: more drinking-water violations or inspections follow this page, and drinkingWaterViolationsNextOffset or inspectionsNextOffset continues it. Absent when neither…
- `waterCompliance` (object): Clean Water Act status. Present only when programs.water is true and ECHO reports a CWA status.
- `zip` (string): ZIP code

### `epa_echo_search_violations` (~566 tokens)

Search EPA Enforcement Cases

ECHO: Search EPA civil and criminal enforcement cases by state, ZIP code, regulatory program, case type, or filing-date range. Returns case identifier, case type and status, lead agency (EPA or state), primary law and section, enforcement outcome, federal and state penalties, cost recovery, compliance action and SEP costs, and filing, settlement, and closing dates. Case rows name no facility: pass a civil caseId to epa_echo_get_case for the facilities, defendants, and settlements behind it. Designed for area-level violation discovery — for a single known facility's enforcement history use epa_echo_get_facility instead. At least one of state or zip_code is required. Page past limit with offset and nextOffset.

Input parameters:

- `case_type` (string): Filter by case type: civil (regulatory violations), criminal (willful violations), or all. A filing-date range covers civil cases only: with a date, all returns civil cases and criminal is rejected.
- `date_filed_end`: Latest filing date, inclusive (YYYY-MM-DD). Civil cases only. Omit, or pass "", for no upper bound.
- `date_filed_start`: Earliest filing date, inclusive (YYYY-MM-DD). Civil cases only. Omit, or pass "", for no lower bound.
- `limit` (integer): Maximum number of cases to return in one call (1–100, default 50). A larger value, up to 5,000, returns 100 with a notice. Page past it with offset and nextOffset.
- `offset` (integer): Number of cases to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `program` (string): Filter by regulatory program: CAA=Clean Air Act, CWA=Clean Water Act, RCRA=hazardous waste, SDWA=drinking water, CERCLA=Superfund, EPCRA=emergency planning and community right-to-know (the statute be…
- `state` (string): 2-letter state or territory code where the case is located (e.g. "TX", "PR"), case-insensitive. A case spanning several states matches each of them. ECHO also files some cases under codes outside the…
- `zip_code` (string): 5-digit ZIP code of the case's facility location; a ZIP+4 such as "98108-1234" searches its first five digits

Output parameters:

- `cases` (array): One page of matching enforcement cases, in ECHO's order: limit cases from offset
- `effectiveQuery` (string): The search as the server sent it upstream, in this tool's input names: normalized values, how each text filter matched ("=" exact, "contains", "begins with", "has words"), and defaults that change wh…
- `error` (object): Present when the call failed. Absent on success.
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when the page is empty: no case matched, or offset is past the last match. Absent when cases are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Present when a filing-date range with case_type "all" left criminal cases out of the results, or when more cases follow (names the offset that continues the list).
- `totalCount` (number): Cases matched before limit and offset were applied

### `epa_echo_get_case` (~294 tokens)

Get EPA Enforcement Case

ECHO: Retrieve one civil enforcement case by case number, with the facilities, defendants, and settlements behind it. Returns the case's category, enforcement type, status, lead agency, relief sought, outcome, court and DOJ docket numbers, summary, and penalty, cost-recovery, compliance-action, and SEP totals; the laws and sections cited; the facilities the case names, one per EPA Registry ID, with address, NAICS and SIC codes, and the program IDs the case links (pass a registryId to epa_echo_get_facility for its compliance profile); defendants; pollutants; milestones; and every enforcement conclusion (order, consent decree, or other settlement) with its facilities, dates, penalties and costs, complying actions, pollutant reductions, and Supplemental Environmental Projects. Take the case number from epa_echo_search_violations or from a formal action's caseId in epa_echo_get_facility. Civil cases only: a criminal CR_ case number fails before any request.

Input parameters:

- `case_id` (string, required): Civil enforcement case number: a caseId from epa_echo_search_violations (e.g. "03-2014-7010", "WA-100446") or a formal action's caseId from epa_echo_get_facility (e.g. "WAPSCAF000000000000000416"). C…

Output parameters:

- `caseCategoryCode` (string): Case category code: "JDC" civil judicial, "AFR" administrative formal
- `caseId` (string): Case number, as ECHO records it
- `caseName` (string): Case name; absent when ECHO records none
- `caseStatus` (string): Case status, e.g. "Final Order Entered", "Resolved", "Closed"
- `caseStatusDate` (string): Date caseStatus took effect (MM/DD/YYYY)
- `complianceActionCostInDollars` (number): Estimated cost of the injunctive relief and other actions required to return to compliance, in dollars
- `costRecoveryInDollars` (number): Response costs the parties agreed or were ordered to repay, in dollars — the outcome of a CERCLA cost-recovery case
- `courtDocketNumber` (string): The court's or region's docket number, e.g. "18-195"
- `defendants` (array): Defendants the case names; empty when ECHO records none, as on many state cases
- `dojDocketNumber` (string): Department of Justice docket number, e.g. "90-11-3-06663/2"
- `enforcementConclusions` (array): Enforcement conclusions in ECHO's order; fields ECHO leaves empty are absent
- `enforcementOutcome` (string): How the case was resolved, e.g. "Final Order No Penalty"
- `enforcementType` (string): Kind of enforcement action, e.g. "Administrative Order", "Bankruptcy", "CERCLA 120E Federal Facility Agreement (FFA)"
- `error` (object): Present when the call failed. Absent on success.
- `facilities` (array): Facilities the case names, one per Registry ID in ECHO's order; a case can span several states
- `lawsAndSections` (array): Statutes and sections the case cites
- `leadAgency` (string): Agency leading the case: "EPA" or "State"
- `milestones` (array): Case milestones in ECHO's order
- `penaltyAssessedInDollars` (number): Federal penalty assessed across the case, in dollars
- `penaltyCollectedInDollars` (number): Penalty ECHO records as collected, in dollars
- `pollutants` (array): Pollutants the case concerns
- `reliefSought` (string): Relief the case seeks, e.g. "Cost Recovery"; absent when ECHO records none
- `stateLocalPenaltyInDollars` (number): State or local penalty assessed across the case, in dollars
- `summary` (string): Case summary as EPA wrote it
- `supplementalProjectCostInDollars` (number): Cost of the Supplemental Environmental Projects agreed, in dollars
- `violations` (string): Violation description ECHO records, e.g. "Oil Spill Violation Under CWA/OPA"

### `epa_tri_get_facility_releases` (~458 tokens)

Get TRI Chemical Releases for Facility

TRI: Query Toxic Release Inventory annual chemical release data for a specific facility. Returns per-chemical release records with chemical name, reporting year, on-site releases by medium (air, water, land, underground injection) and their total, and the Form R one-time release quantity, newest reporting year first; page past 500 filings with offset and nextOffset. Quantities are in pounds; dioxin and dioxin-like compounds, which TRI has facilities report in grams, are converted and marked with reportedUnit. TRI data lags ~18 months — the most recent available year is typically 2 years prior to the current calendar year. facility_id is the 15-character TRI facility ID: take it from the facilityId field of epa_tri_search_releases, or from the triFacilityIds of epa_echo_get_facility for a facility you already hold a Registry ID for. A 12-digit EPA Registry ID is a different identifier and matches no TRI records.

Input parameters:

- `chemical_name` (string): Optional filter to restrict results to a specific chemical (partial match, case-insensitive). Commas, apostrophes, brackets, and Greek letters are matched as written, e.g. "4,4'-Methylenedianiline".
- `facility_id` (string, required): 15-character TRI facility ID (e.g. "9867WSNRWD1981S") — the facilityId field returned by epa_tri_search_releases, or an entry of epa_echo_get_facility triFacilityIds. A 12-digit EPA Registry ID match…
- `limit` (integer): Maximum number of filings to return in one call (1–500, default 500). A larger value, up to 5,000, returns 500 with a notice. Page past it with offset and nextOffset.
- `offset` (integer): Number of filings to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `year` (integer): Reporting year to retrieve. Omit for all available years, newest first. TRI data is typically available through ~2 years prior to the current year.

Output parameters:

- `cap` (number): The limit that was applied. Present only when truncated.
- `error` (object): Present when the call failed. Absent on success.
- `facilityId` (string): TRI facility ID queried
- `guidance` (string): Present when truncated: the offset that continues the lookup.
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when no releases are found — suggests alternative years or checking the facility ID. Absent when releases are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Present when a punctuated chemical_name filter read its page limit before finding every filing spelled as given, so more matching records may exist.
- `releases` (array): Per-chemical annual TRI release records for the queried facility
- `shown` (number): Number of filings returned. Present only when truncated.
- `truncated` (boolean): True when more filings may follow this page; nextOffset continues it. Absent on the last page.

### `epa_tri_search_releases` (~589 tokens)

Search TRI Releases by Region

TRI: Search Toxic Release Inventory data across facilities in a state or county, for one reporting year or, without year, every available year newest first. Returns facility name and county, TRI facility ID, EPA Registry ID (the registry_id epa_echo_get_facility takes), parent company, chemical name, and the Form R one-time release quantity; set include_release_breakdown to add on-site releases by medium (air, water, land, and underground-injection) and their total, at the cost of a slower call. Quantities are in pounds; dioxin and dioxin-like compounds, which TRI has facilities report in grams, are converted and marked with reportedUnit. Records come in reporting-year order, not ranked by quantity; page through a large match with offset and nextOffset. Use to build an environmental exposure profile for an area. Use epa_tri_get_facility_releases for detailed release records for a single facility. TRI data lags ~18 months from the current calendar year.

Input parameters:

- `chemical_name` (string): Optional filter to restrict results to a specific chemical (partial match, case-insensitive). Commas, apostrophes, brackets, and Greek letters are matched as written, e.g. "4,4'-Methylenedianiline".
- `county` (string): County name to narrow results within the state, e.g. "King". Case-insensitive partial match on the bare name, so a short name can also match a longer one (LAKE matches LAKE OF THE WOODS; check county…
- `include_release_breakdown` (boolean): Add on-site releases by medium (air, water, land, underground injection) and their total to each record. Costs one extra upstream request, which can add about 20 seconds when EPA has not cached it.
- `limit` (integer): Maximum number of release records to return in one call (1–200, default 50). A larger value, up to 5,000, returns 200 with a notice. Page past it with offset and nextOffset.
- `offset` (integer): Number of release records to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `state` (string, required): 2-letter US state or territory code (required, e.g. "WA", "TX", "PR")
- `year` (integer): Reporting year. Omit to search every available year in the state, newest reporting year first, so the first page holds the most recent filings. TRI data lags ~18 months.

Output parameters:

- `cap` (number): The limit that was applied. Present only when truncated.
- `effectiveQuery` (string): The search as the server sent it upstream, in this tool's input names: normalized values, how each text filter matched ("=" exact, "contains", "begins with", "has words"), and defaults that change wh…
- `error` (object): Present when the call failed. Absent on success.
- `guidance` (string): Present when truncated: the offset that continues the search.
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when no releases are found. Absent when releases are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Present when a punctuated county or chemical_name filter read its page limit before finding limit matches spelled as given, so more matching records may exist.
- `releases` (array): TRI release records for facilities in the queried state and filters
- `shown` (number): Number of release records returned. Present only when truncated.
- `state` (string): State queried, as the search sent it (uppercase code)
- `truncated` (boolean): True when more release records may follow this page; nextOffset continues it. Absent on the last page.

### `epa_superfund_search_sites` (~479 tokens)

Search Superfund Sites

Superfund: Search Superfund (CERCLA/SEMS) sites by location or NPL listing status. Returns site identifiers, source NPL status labels, non-NPL assessment and archive metadata when available, coordinates, and county FIPS code. Accepts either state/city/ZIP, returned in SEMS site ID order, or latitude+longitude+radius for proximity searches, ordered nearest first; page through a large match with offset and nextOffset. Proximity searches match only sites with usable coordinates and share a 50-second deadline; narrow the radius or add filters if the search times out. Use epa_superfund_get_site with a returned siteId for contaminant associations.

Input parameters:

- `city` (string): City name (partial match within state, case-insensitive). Punctuation is matched as written ("St. Louis", "Coeur d'Alene").
- `exclude_archived` (boolean): Exclude archived sites by requiring SEMS archived_ind=N; false retains all archive statuses
- `latitude` (number): Latitude for proximity search (use with longitude and radius_miles)
- `limit` (integer): Maximum number of sites to return in one call (1–200, default 50). A larger value, up to 5,000, returns 200 with a notice. Page past it with offset and nextOffset.
- `longitude` (number): Longitude for proximity search (use with latitude and radius_miles)
- `npl_status` (string): Filter by NPL status: listed=final or part of an NPL site (F,A), not-listed=N, proposed=P, deleted=D, removed=R, withdrawn=W, all=include all
- `offset` (integer): Number of sites to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `radius_miles` (number): Search radius in miles from the given coordinates. Required when using latitude+longitude.
- `state` (string): 2-letter US state or territory code to filter sites, e.g. "WA"
- `zip_code` (string): 5-digit ZIP code to filter sites; a ZIP+4 searches its 5-digit ZIP. Matches sites recorded under the ZIP or any ZIP+4 within it.

Output parameters:

- `effectiveQuery` (string): The search as the server sent it upstream, in this tool's input names: normalized values, how each text filter matched ("=" exact, "contains", "begins with", "has words"), and defaults that change wh…
- `error` (object): Present when the call failed. Absent on success.
- `guidance` (string): Present when more sites follow: the offset that continues the search.
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when no sites are found. Absent when sites are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Proximity coordinate-coverage limitation, with the page's place in the nearest-first match when the page does not hold every match; also present when a non-proximity punctuated city filter reaches it…
- `sites` (array): Matching Superfund sites
- `totalCount` (number): Superfund sites matched before limit and offset. Present for a proximity search, which ranks every match; absent for a state, city, or ZIP search, whose match count EPA's DMAP service does not report…

### `epa_superfund_get_site` (~123 tokens)

Get Superfund Site

Superfund: Retrieve a site by exact SEMS site ID from epa_superfund_search_sites, with all recorded contaminant associations and their media, operable units, and EPA actions. Repeated chemicals in different contexts and duplicate source rows are retained. An existing site without contaminant records returns an empty array. Associations do not establish current exposure, concentrations, or current risk. Site and contaminant requests share a 50-second deadline.

Input parameters:

- `site_id` (string, required): SEMS siteId from epa_superfund_search_sites; preserve leading zeros

Output parameters:

- `contaminants` (array): Every SEMS contaminant association for the site; empty when none are recorded
- `error` (object): Present when the call failed. Absent on success.
- `site` (object): Superfund site record with source status and assessment metadata

### `epa_water_search_systems` (~643 tokens)

Search Drinking Water Systems

Drinking water (SDWIS): Search public water systems by state, ZIP code, or county served. Returns system name, PWSID, population served, primary water source, and activity status, in PWSID order; page through a large match with offset and nextOffset. zip_code matches a system's address of record, often an owner's or operator's office far from the area it serves; county (with state) finds the systems serving an area. With has_violation, returns only systems with a current SDWA violation in EPA's ECHO, which applies EPA's Enforcement Response Policy, so it flags fewer systems than SDWIS's unresolved violation records. At least one of state or zip_code is required. Pass a returned pwsid as registry_id to epa_echo_get_facility for the system's drinking-water violations: contaminant, measured level, MCL, status, and enforcement actions.

Input parameters:

- `active_only` (boolean): When true, return only systems SDWIS lists as active (activity code A). The default false also returns inactive systems and ones changed to non-public.
- `county` (string): County the system serves, e.g. "King"; requires state. Case-insensitive, and a trailing "County", "Parish", "Borough", "City and Borough", "Census Area", or "Municipality" is dropped, so "Anchorage"…
- `has_violation` (boolean): When true, return only systems with a current SDWA violation in EPA's ECHO, its compliance status for the system, which clears violations SDWIS still lists as unresolved. Works with state, zip_code,…
- `limit` (integer): Maximum number of systems to return in one call (1–200, default 50). A larger value, up to 5,000, returns 200 with a notice. Page past it with offset and nextOffset.
- `offset` (integer): Number of systems to skip before this page (default 0). Pass the previous call's nextOffset to continue; a value past the end returns an empty page.
- `pws_type` (string): Filter by public water system type: community=permanent residences, non-transient=regular non-residents (schools, businesses), transient=occasional users (campgrounds)
- `state` (string): 2-letter US state or territory code (e.g. "WA", "CA"), or an EPA region code 01–10 for tribal systems, whose primacy agency is the EPA region
- `zip_code` (string): 5-digit ZIP code of the water system's address of record — often its owner's or operator's office, which can be far from the area the system serves. Matches stored ZIP+4s within it; a ZIP+4 searches…

Output parameters:

- `effectiveQuery` (string): The search as the server sent it upstream, in this tool's input names: normalized values, how each text filter matched ("=" exact, "contains", "begins with", "has words"), and defaults that change wh…
- `error` (object): Present when the call failed. Absent on success.
- `guidance` (string): Present when more systems may follow: the offset that continues the search.
- `limitNotice` (string): Present when limit was above the cap and the call returned the cap instead.
- `message` (string): Recovery hint when no systems are found. Absent when systems are returned.
- `nextOffset` (number): Offset of the next page: present exactly when more records follow, absent on the last page. Pass it as offset to continue.
- `notice` (string): Present when a check ended before the page filled: a ZIP or county has_violation check read its 1,000-system limit, or the county check reached its read limit with few whole-name matches. nextOffset…
- `systems` (array): Matching public water systems, in PWSID order
- `totalCount` (number): Systems matched before limit and offset. Present when the call saw the whole match: a search from offset 0 with no nextOffset, or a state has_violation search that checked ECHO's whole violator list.…

### `epa_ejscreen_get_scores` (~364 tokens)

Get EJScreen Environmental Justice Indicators

EJScreen: Get EJScreen environmental-justice indicators for a point and surrounding buffer. Returns the 13 EJScreen environmental indicators (PM2.5, ozone, diesel particulate, NO2, lead paint, traffic proximity, Superfund/RMP/hazardous-waste/wastewater proximity, underground storage tanks, drinking-water non-compliance, and RSEI toxic air releases) and 6 demographic indicators (people of color, low income, limited English, less than high school, under 5, over 64), each with national and state percentiles plus EJ Index values, along with the Demographic Index and Supplemental Demographic Index and their national averages. Data is EJAM's ACS 2018–2022 release of EJScreen (EJScreen 2.3 method), served via the community-maintained EJAM API (api.ejanalysis.com, Public Environmental Data Partners), which rehosts EJScreen after EPA discontinued public access in 2025 — not a live EPA endpoint. Provide latitude, longitude, and a buffer distance (default 1 mile, max 15 miles). When EJScreen has no indicators — the point is outside its US bounds, the buffer holds no Census block, or its blocks have no residents — coverage.valid is false and coverage.note says which, and whether a larger distance may help.

Input parameters:

- `distance` (number): Buffer radius around the point, in the given unit (default 1). The miles-equivalent must be 15 or less.
- `latitude` (number, required): Latitude in decimal degrees.
- `longitude` (number, required): Longitude in decimal degrees.
- `unit` (string): Unit for distance. Kilometers are converted to miles (× 0.621371) before the request.

Output parameters:

- `coverage` (object): Whether EJScreen data was available for the buffer.
- `dataSource` (string): Provenance and vintage of the data.
- `demographic` (array): EJScreen demographic indicators present for the area. Empty when coverage.valid is false.
- `demographicIndex` (object): EJScreen Demographic Index. Absent when coverage.valid is false.
- `environmental` (array): EJScreen environmental indicators present for the area. Empty when coverage.valid is false.
- `error` (object): Present when the call failed. Absent on success.
- `location` (object): The queried location and buffer context.
- `notice` (string): The coverage note, repeated as guidance, when no indicators were returned.
- `reportUrl` (string): Link (absolute URL) to the full EJScreen report for this point and buffer.
- `supplementalDemographicIndex` (object): EJScreen Supplemental Demographic Index. Absent when coverage.valid is false.

### `epa_airnow_get_conditions` (~371 tokens)

Get Air Quality Index

AirNow: Get current AQI observations or the issued AQI forecast for a US location from the AirNow API. Provide either zip_code or both latitude and longitude. Current mode returns the latest hourly NowCast AQI for each pollutant (PM2.5, ozone, PM10) from the closest monitor within 50 miles, with the monitor site name, site ID, and reporting agency; pollutants in one reporting area can come from different monitors. Forecast mode returns every day the reporting area's agency has issued (typically one to five), each with the AQI category, an action-day flag, and the issue date, plus the agency's forecast discussion when one exists; some agencies forecast a category without an AQI number. In forecast mode, set forecast_date to keep only the forecast valid on that date; current mode rejects it. Data are preliminary — suitable for awareness, not regulatory, trend, or enforcement decisions. Responses are cached for ~1 hour.

Input parameters:

- `forecast_date` (string): Forecast mode only (rejected in current mode): keep just the forecast valid on this date (YYYY-MM-DD). Omit to get every issued forecast day. When no issued day matches, the result is empty and its m…
- `latitude` (number): Latitude in decimal degrees (use with longitude instead of zip_code)
- `longitude` (number): Longitude in decimal degrees (use with latitude instead of zip_code)
- `mode` (string): Data mode: "current" for the latest hourly observations, "forecast" for every forecast day the reporting area has issued
- `zip_code` (string): US ZIP code for the location: 5 digits, or a ZIP+4 such as 98108-1234, which searches its first five digits

Output parameters:

- `attribution` (string): Credit to the reporting or forecasting agencies and the U.S. EPA AirNow program, with the preliminary-data notice. Reproduce it when passing this data on.
- `error` (object): Present when the call failed. Absent on success.
- `message` (string): Why the result is empty: AirNow's own explanation, the issued forecast dates when forecast_date matched none, or a coverage hint. Absent when data is returned.
- `mode` (string): Data mode used: "current" or "forecast"
- `observations` (array): AQI observation or forecast records grouped by reporting area

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa#diagnostics

## Score history

- 2026-11-05: 75
- 2026-11-04: 75
- 2026-11-03: 75
- 2026-11-02: 75
- 2026-11-01: 75
- 2026-10-31: 72
- 2026-10-30: 72
- 2026-10-29: 72
- 2026-10-28: 72
- 2026-10-27: 72
- 2026-10-26: 72
- 2026-10-25: 72
- 2026-10-24: 72
- 2026-10-23: 79
- 2026-10-22: 78
- 2026-10-21: 78
- 2026-10-20: 78
- 2026-10-19: 77
- 2026-10-18: 77
- 2026-10-17: 76
- 2026-10-16: 24
- 2026-10-15: 75
- 2026-10-14: 75
- 2026-10-13: 74
- 2026-10-12: 74
- 2026-10-10: 73
- 2026-10-09: 72

## Common questions

### What is the io.github.cyanheads/epa-mcp-server server?

io.github.cyanheads/epa-mcp-server is listed in the public MCP registry as io.github.cyanheads/epa-mcp-server. Search EPA compliance, TRI, Superfund, drinking-water, EJScreen, and AirNow data. This page covers its hosted endpoint (https://epa.caseyjhand.com/mcp).

### Is the io.github.cyanheads/epa-mcp-server server safe to use?

io.github.cyanheads/epa-mcp-server scores 75 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the io.github.cyanheads/epa-mcp-server server expose?

io.github.cyanheads/epa-mcp-server exposes 11 tools: epa_echo_search_facilities, epa_echo_get_facility, epa_echo_search_violations, epa_echo_get_case, epa_tri_get_facility_releases, and 6 more. Their descriptions and schemas cost roughly 5,262 tokens of context every time the server is loaded.

### Does the io.github.cyanheads/epa-mcp-server server require authentication?

No. We connected to io.github.cyanheads/epa-mcp-server without credentials and it answered, so anything it exposes is reachable by anyone who knows the address.

### Is the io.github.cyanheads/epa-mcp-server server still maintained?

io.github.cyanheads/epa-mcp-server is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- Remote endpoint: https://epa.caseyjhand.com/mcp
- Repository: https://github.com/cyanheads/epa-mcp-server
- Changelog RSS feed: https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa.xml
- Changelog JSON feed: https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa.json
- HTML version of this page: https://verifymcp.io/servers/cyanheads-epa-mcp-server/epa
