About epa-mcp-server
epa-mcp-server provides MCP access to EPA environmental datasets, including facility compliance, toxic releases, Superfund, drinking water, EJScreen and AirNow. It is intended for users researching facilities, communities and environmental conditions.
Key features
- 01Search regulated facilities and violations
- 02Retrieve facility compliance profiles
- 03Query TRI and Superfund records
- 04Search drinking water systems
- 05Get EJScreen indicators
- 06Retrieve AirNow conditions and forecasts
Use cases
- 01Review inspections and enforcement at a facility
- 02Compare toxic releases across a state or county
- 03Find nearby Superfund sites or drinking water systems
- 04Screen a location for environmental and demographic indicators
MCP support
Exposes nine tools and two resources to MCP clients over STDIO or Streamable HTTP. AirNow conditions require an API key in self-hosted setups; the public instance omits that tool.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://epa.caseyjhand.com/mcpclaude mcp add --transport http cyanheads-epa https://epa.caseyjhand.com/mcp{"mcpServers":{"cyanheads-epa":{"type":"http","url":"https://epa.caseyjhand.com/mcp"}}}Open in Cursor{"servers":{"cyanheads-epa":{"type":"http","url":"https://epa.caseyjhand.com/mcp"}}}Open in VS Code{"mcpServers":{"cyanheads-epa":{"type":"http","url":"https://epa.caseyjhand.com/mcp"}}}{"mcpServers":{"cyanheads-epa":{"serverUrl":"https://epa.caseyjhand.com/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
9| Parameter | Type | Required | Description |
|---|---|---|---|
| city | string | No | City name, case-insensitive; matches cities that begin with it ("TACOMA", or "SEAT" for SEATTLE and SEATAC). Pair with state. |
| limit | integer | No | Maximum number of facilities to return (1–100) |
| state | string | No | 2-letter US state abbreviation (e.g. "WA", "CA") |
| latitude | number | No | Latitude in decimal degrees. Pair with longitude and radius_miles for proximity search. |
| programs | array | No | 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, SDWA=drinking water. |
| zip_code | string | No | 5-digit ZIP code to search within |
| longitude | number | No | Longitude in decimal degrees. Pair with latitude and radius_miles for proximity search. |
| active_only | boolean | No | When true, return only facilities with an active permit |
| radius_miles | number | No | Search radius in miles around the given latitude/longitude (max 100). Requires latitude and longitude. |
| has_violation | boolean | No | When true, return only facilities with at least one program in significant noncompliance (complianceStatus "Significant Violation") |
Details
From the project README@cyanheads/epa-mcp-server
Search and retrieve EPA environmental data: facility compliance (ECHO), toxic releases (TRI), Superfund sites, drinking water systems, environmental-justice screening (EJScreen), and real-time air quality (AirNow). STDIO or Streamable HTTP.
Public Hosted Server: https://epa.caseyjhand.com/mcp
Overview
EPA environmental data across five federal programs — facility compliance (ECHO), toxic releases (TRI), Superfund sites, drinking water systems (SDWIS), and environmental-justice screening (EJScreen) — plus real-time AirNow air quality. Pull a facility's inspection and enforcement history, track toxic releases across a region, or screen any point for environmental-justice risk.
Tools
| Tool | Description |
|---|---|
epa_echo_search_facilities | Search EPA-regulated facilities by location, environmental program, or compliance status across CAA, CWA, RCRA, TRI, and SDWA |
epa_echo_get_facility | Full compliance profile for one facility by EPA Registry ID — compliance status, inspections, enforcement actions, penalties, and TRI IDs |
epa_echo_search_violations | Search EPA civil and criminal enforcement cases by state, program, or date range |
epa_airnow_get_conditions | Current AQI observations or the issued AQI forecast from AirNow |
epa_tri_get_facility_releases | Per-chemical Toxic Release Inventory data for a single facility |
epa_tri_search_releases | Toxic Release Inventory records across facilities in a state or county |
epa_superfund_search_sites | Search Superfund (CERCLA/SEMS) sites by location or NPL listing status |
epa_water_search_systems | Search drinking water systems (SDWIS) by state or ZIP code |
epa_ejscreen_get_scores | EJScreen environmental-justice indicators for a point and buffer |
Resources
| Resource | Description |
|---|---|
epa://facility/{registry_id} | Full compliance profile for a facility by EPA Registry ID (same data as epa_echo_get_facility) |
epa://superfund/{site_id} | Superfund site record by SEMS site ID |
All resource data is also reachable via tools — use epa_echo_get_facility and epa_superfund_search_sites for programmatic access in tool-only MCP clients.
Capability reference
epa_echo_search_facilities tool
- Provide ZIP code, state, city (prefix match, preferably with state), or latitude + longitude + radius_miles (max 100 miles); up to 100 results (default 50)
- Returns
registryIdforepa_echo_get_facility, county andfipsCode, coordinates, per-program flags, and five-year inspection and penalty totals programstakes one of CAA, CWA, RCRA, TRI, or SDWA;has_violationkeeps facilities in significant noncompliance;active_onlykeeps facilities with an active permit
epa_echo_get_facility tool
registry_idcomes fromepa_echo_search_facilities- Returns
complianceStatus, a per-statuteprogramCompliancesummary, the newest 100 inspections from the last five years (inspectionsTotalcounts them all), five years of enforcement actions with penalties, the latest TRI total withtriReleasesTransfersYear, andtriFacilityIdsforepa_tri_get_facility_releases - If the status lookup fails, the profile returns without
complianceStatusand anoticesays so; an unknown ID fails asfacility_not_found
epa_echo_search_violations tool
- Requires
stateorzip_code; up to 100 cases (default 50) programfilters CAA, CWA, RCRA, SDWA, CERCLA, FIFRA, or TSCA, matching any law a case cites;case_typeis civil, criminal, or all (default)date_filed_start/date_filed_end(YYYY-MM-DD, inclusive) bound the filing date. Only civil cases carry filing dates, so with a dateallreturns civil cases with anotice, andcriminalis rejected
epa_airnow_get_conditions tool
- Provide
zip_codeor bothlatitudeandlongitude;modeis current (default) or forecast - Current mode returns each pollutant's latest hourly NowCast AQI from the closest monitor within 50 miles; forecast mode returns each issued day with
dateValid,actionDay, anddiscussion, andforecast_date(YYYY-MM-DD) keeps one day - Readings carry
categoryNameandcategoryNumber(1 Good–6 Hazardous);attributionmarks the data preliminary and unsuitable for regulatory or enforcement decisions - Registered only when
AIRNOW_API_KEYis set; responses cache for about one hour
epa_tri_get_facility_releases tool
facility_idis the TRIfacilityIdfromepa_tri_search_releasesorepa_echo_get_facilitytriFacilityIds; optionalyear(1987–2030) and partialchemical_name- Returns per-chemical air, water, land, and underground-injection releases plus a one-time/non-routine total, newest reporting year first when
yearis omitted. At 500 filings, both response surfaces warn that more may exist; narrow withyearorchemical_name - Quantities are in pounds; dioxin records reported in grams are converted and carry
reportedUnit: "grams"
epa_tri_search_releases tool
- Requires 2-letter
state; optionalcounty(partial match, case-insensitive), partialchemical_name, andyear; up to 200 records (default 50), newest reporting year first whenyearis omitted - Returns
facilityIdforepa_tri_get_facility_releasesand each record's one-time/non-routine total;include_release_breakdown: trueadds air, water, land, and underground-injection releases at the cost of one more upstream request (up to ~20 seconds when EPA has not cached it) - Dioxin records reported in grams are converted to pounds and carry
reportedUnit: "grams"
epa_superfund_search_sites tool
- Provide
state/city/zip_code, orlatitude+longitude+radius_miles(0.1–500); up to 200 sites (default 50) - Returns site identifiers, location, NPL, and cleanup status;
npl_statusfilters listed, not-listed, proposed, or all (default) - Proximity searches page latitude/longitude candidates to exhaustion, apply exact distance, and return the nearest sites first. A
noticesays how many matching sites the result limit omitted; sites without coordinates cannot be located. All candidate pages share a 50-second deadline — narrow the radius or add filters if it times out
epa_water_search_systems tool
- Requires
stateorzip_code; up to 200 systems (default 50).zip_codematches each system's address of record, not its service area has_violationkeeps systems with an open SDWIS violation;pws_typeis community, non-transient, or transient, returned as CWS / NTNCWS / TNCWS intypehas_violationreads at most 1,000 records; when that fills, anoticesays the list may miss violating systems
epa_ejscreen_get_scores tool
- Requires
latitudeandlongitude;distancedefaults to 1,unitto miles (kilometers accepted); buffer capped at 15 miles - Returns 13 environmental and 6 demographic indicators with national/state percentiles and demographic indices; outside coverage,
coverage.valid: falsecarries a note - EJScreen v2.2 (2022) comes from the community-maintained EJAM API (Public Environmental Data Partners)
epa://facility/{registry_id} resource
registry_idcomes fromepa_echo_search_facilities- Returns the
epa_echo_get_facilitycompliance profile; errors when no facility resolves
epa://superfund/{site_id} resource
site_idcomes fromepa_superfund_search_sites- Returns the Superfund site record; errors when no SEMS record resolves
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
EPA-specific:
- One
epa_tool surface over ECHO (facility compliance), Envirofacts DMAP (TRI, Superfund, SDWIS), AirNow (air quality), and the community-maintained EJAM API (EJScreen v2.2, 2022) - AirNow responses cache for about one hour to stay within per-key rate limits
- TRI reporting lags about 18 months; the latest reporting year is typically two years behind
Agent-friendly output:
- Search results carry the IDs the next tool needs:
registryId,fipsCode,facilityId, andtriFacilityIds epa_echo_get_facilityreturns a partial profile with anoticewhen the status lookup fails- Empty searches return a
messagethat echoes the applied filters and suggests how to broaden them
Getting started
Public Hosted Instance
A public instance is available at https://epa.caseyjhand.com/mcp — no installation required. It runs without an AirNow key, so epa_airnow_get_conditions is not available there; the other 8 tools are. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"epa-mcp-server": {
"type": "streamable-http",
"url": "https://epa.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file. An AirNow API key is optional — set AIRNOW_API_KEY to enable epa_airnow_get_conditions (register free at docs.airnowapi.org); without it the server starts with the other 8 tools. ECHO, DMAP, and EJScreen tools work without authentication.
{
"mcpServers": {
"epa-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/epa-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"AIRNOW_API_KEY": "your-airnow-key"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"epa-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/epa-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"AIRNOW_API_KEY": "your-airnow-key"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"epa-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "AIRNOW_API_KEY=your-airnow-key",
"ghcr.io/cyanheads/epa-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 AIRNOW_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+); development and Docker use Bun v1.4.4.
- (Optional) An AirNow API key to enable
epa_airnow_get_conditions— register free at docs.airnowapi.org/account/request. Without it the server runs the other 8 tools. ECHO, DMAP, and EJScreen tools require no API key.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/epa-mcp-server.git
- Navigate into the directory:
cd epa-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# optionally set AIRNOW_API_KEY to enable the air quality tool
Configuration
All configuration is validated at startup via Zod schemas in src/config/. Key environment variables:
| Variable | Description | Default |
|---|---|---|
AIRNOW_API_KEY | Optional. Enables epa_airnow_get_conditions. | — |
EPA_ECHO_BASE_URL | ECHO API base URL | https://echodata.epa.gov/echo |
EPA_DMAP_BASE_URL | Envirofacts DMAP API base URL | https://data.epa.gov/dmapservice |
EPA_AIRNOW_BASE_URL | AirNow API base URL | https://www.airnowapi.org/aq |
EJSCREEN_API_BASE_URL | EJScreen (EJAM) API base URL | https://api.ejanalysis.com |
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path | /mcp |
MCP_SESSION_MODE | HTTP sessions: auto, stateful, or stateless | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (RFC 5424) | info |
LOG_TOOL_FAILURE_PAYLOADS | Log failed calls' arguments and results with key-name redaction. Secrets inside free-form values remain visible. | false |
LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES | Byte cap per logged payload | 16384 |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
OTEL_ENABLED | Enable OpenTelemetry tracing and metrics | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL | — |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | Per-signal endpoint overrides | — |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | Enables OTLP log export | — |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t epa-mcp-server .
docker run --rm -e AIRNOW_API_KEY=your-key -p 3010:3010 epa-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/epa-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Nine tools across ECHO, DMAP, EJAM, and AirNow. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Facility and Superfund URI handlers. |
src/services/echo | ECHO REST API service layer — facility search, facility detail, enforcement cases. |
src/services/dmap | Envirofacts DMAP service layer — TRI releases, Superfund sites, drinking water systems. |
src/services/airnow | AirNow service layer — current and forecast AQI observations. |
src/services/ejscreen | EJScreen (EJAM) service layer — environmental-justice indicators for a point + buffer. |
tests/ | Unit and integration tests mirroring src/. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools and resources via the barrels in
src/mcp-server/*/definitions/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
- ECHO searches must enforce at least one geographic parameter — unscoped queries time out against the live API
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.