io.github.cyanheads/epa-mcp-server
Search EPA compliance, TRI, Superfund, drinking-water, EJScreen, and AirNow data.
What can you do with io.github.cyanheads/epa-mcp-server?
- 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
- 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
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
claude mcp add --transport http epa-mcp-server 'https://epa.caseyjhand.com/mcp'
{
"mcpServers": {
"epa-mcp-server": {
"type": "http",
"url": "https://epa.caseyjhand.com/mcp"
}
}
}
@cyanheads/epa-mcp-serverclaude mcp add epa-mcp-server -e 'AIRNOW_API_KEY=<AIRNOW_API_KEY>' -e 'EJSCREEN_API_BASE_URL=https://api.ejanalysis.com' -e 'MCP_LOG_LEVEL=info' -- npx -y @cyanheads/epa-mcp-server@0.7.0
{
"mcpServers": {
"epa-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/epa-mcp-server@0.7.0"
],
"env": {
"AIRNOW_API_KEY": "<AIRNOW_API_KEY>",
"EJSCREEN_API_BASE_URL": "https://api.ejanalysis.com",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Environment variables
AIRNOW_API_KEYOptional AirNow API key for real-time and forecast air quality data. When unset, the epa_airnow_get_conditions tool is not registered and the other tools run without it. Free registration at docs.airnowapi.org.EJSCREEN_API_BASE_URLOptional override for the EJScreen (EJAM) API base URL used by epa_ejscreen_get_scores. Defaults to https://api.ejanalysis.com — the community-maintained EJScreen rehost by Public Environmental Data Partners.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
@cyanheads/epa-mcp-serverclaude mcp add epa-mcp-server -e 'MCP_TRANSPORT_TYPE=<MCP_TRANSPORT_TYPE>' -e 'AIRNOW_API_KEY=<AIRNOW_API_KEY>' -e 'EJSCREEN_API_BASE_URL=https://api.ejanalysis.com' -e 'MCP_HTTP_HOST=127.0.0.1' -e 'MCP_HTTP_PORT=3010' -e 'MCP_HTTP_ENDPOINT_PATH=/mcp' -e 'MCP_AUTH_MODE=none' -e 'MCP_LOG_LEVEL=info' -- npx -y @cyanheads/epa-mcp-server@0.7.0
{
"mcpServers": {
"epa-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/epa-mcp-server@0.7.0"
],
"env": {
"MCP_TRANSPORT_TYPE": "<MCP_TRANSPORT_TYPE>",
"AIRNOW_API_KEY": "<AIRNOW_API_KEY>",
"EJSCREEN_API_BASE_URL": "https://api.ejanalysis.com",
"MCP_HTTP_HOST": "127.0.0.1",
"MCP_HTTP_PORT": "3010",
"MCP_HTTP_ENDPOINT_PATH": "/mcp",
"MCP_AUTH_MODE": "none",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Environment variables
MCP_TRANSPORT_TYPESelects the HTTP transport.AIRNOW_API_KEYOptional AirNow API key for real-time and forecast air quality data. When unset, the epa_airnow_get_conditions tool is not registered and the other tools run without it. Free registration at docs.airnowapi.org.EJSCREEN_API_BASE_URLOptional override for the EJScreen (EJAM) API base URL used by epa_ejscreen_get_scores. Defaults to https://api.ejanalysis.com — the community-maintained EJScreen rehost by Public Environmental Data Partners.MCP_HTTP_HOSTThe hostname for the HTTP server.MCP_HTTP_PORTThe port to run the HTTP server on.MCP_HTTP_ENDPOINT_PATHThe endpoint path for the MCP server.MCP_AUTH_MODEAuthentication mode to use: 'none', 'jwt', or 'oauth'.MCP_LOG_LEVELSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
@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.
9 Tools • 2 Resources
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.
Frequently asked questions
What is io.github.cyanheads/epa-mcp-server?
Search EPA compliance, TRI, Superfund, drinking-water, EJScreen, and AirNow data.
How do I install io.github.cyanheads/epa-mcp-server?
It runs as a remote server. In Claude Code: claude mcp add --transport http epa-mcp-server 'https://epa.caseyjhand.com/mcp'. For other clients, paste the JSON configuration from this page into their MCP settings.
Does io.github.cyanheads/epa-mcp-server need an API key?
Its registry entry lists no required key or secret. Some features may still need an account with the service it connects to.
Is io.github.cyanheads/epa-mcp-server open source?
Yes. Its code is on GitHub under the Apache-2.0 license.
Who maintains io.github.cyanheads/epa-mcp-server?
It is published by cyanheads. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.