@cyanheads/epa-mcp-server 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +416 -0
- package/CLAUDE.md +38 -20
- package/Dockerfile +108 -37
- package/README.md +102 -97
- package/changelog/0.3.x/0.3.2.md +40 -0
- package/changelog/0.3.x/0.3.3.md +34 -0
- package/changelog/template.md +9 -26
- package/dist/config/server-config.d.ts +2 -2
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +6 -8
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +2 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js +2 -6
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js +2 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-facility.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-facility.tool.js +1 -3
- package/dist/mcp-server/tools/definitions/get-facility.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +2 -10
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts +0 -5
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js +2 -10
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-superfund.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js +2 -6
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-violations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-violations.tool.js +1 -3
- package/dist/mcp-server/tools/definitions/search-violations.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js +1 -3
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js.map +1 -1
- package/dist/services/airnow/airnow-service.d.ts.map +1 -1
- package/dist/services/airnow/airnow-service.js +2 -2
- package/dist/services/airnow/airnow-service.js.map +1 -1
- package/dist/services/dmap/dmap-service.d.ts.map +1 -1
- package/dist/services/dmap/dmap-service.js +2 -2
- package/dist/services/dmap/dmap-service.js.map +1 -1
- package/dist/services/echo/echo-service.d.ts.map +1 -1
- package/dist/services/echo/echo-service.js +11 -9
- package/dist/services/echo/echo-service.js.map +1 -1
- package/dist/services/ejscreen/ejscreen-service.d.ts.map +1 -1
- package/dist/services/ejscreen/ejscreen-service.js +7 -9
- package/dist/services/ejscreen/ejscreen-service.js.map +1 -1
- package/package.json +14 -11
- package/server.json +10 -24
- package/dist/epa-mcp-server.mcpb +0 -0
package/Dockerfile
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
# This stage installs all dependencies (including dev), builds the TypeScript
|
|
5
5
|
# source code into JavaScript, and prepares the production assets.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Pinned to $BUILDPLATFORM rather than the target platform: `bun run build` emits
|
|
8
|
+
# JavaScript, and only `dist/` crosses into the production stage. Built for the
|
|
9
|
+
# target instead, the non-native leg of a `--platform linux/amd64,linux/arm64`
|
|
10
|
+
# build runs under QEMU, where bun >= 1.4 aborts with a JavaScriptCore allocator
|
|
11
|
+
# assertion and fails the multi-arch push.
|
|
12
|
+
#
|
|
13
|
+
# The constraint this assumes: the build stage produces platform-independent
|
|
14
|
+
# output. A stage that compiles a native addon needs the target-arch toolchain
|
|
15
|
+
# and cannot cross-compile this way — drop the flag there.
|
|
10
16
|
# ==============================================================================
|
|
11
|
-
FROM --platform=$BUILDPLATFORM oven/bun:1.4.
|
|
17
|
+
FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS build
|
|
12
18
|
|
|
13
19
|
WORKDIR /usr/src/app
|
|
14
20
|
|
|
@@ -27,19 +33,92 @@ COPY . .
|
|
|
27
33
|
RUN bun run build
|
|
28
34
|
|
|
29
35
|
|
|
36
|
+
# ==============================================================================
|
|
37
|
+
# Production Dependencies Stage
|
|
38
|
+
#
|
|
39
|
+
# Installs the production dependency tree for the target platform. Every step
|
|
40
|
+
# here can run JavaScript — bunfig.toml's security scanner runs as a Bun
|
|
41
|
+
# program, and so do the OTel and musl-prune scripts — so the stage runs on
|
|
42
|
+
# $BUILDPLATFORM and cross-installs with `--os`/`--cpu`, which pick each
|
|
43
|
+
# platform-specific optional dependency (native bindings such as DuckDB's) for
|
|
44
|
+
# the target. Only `node_modules` leaves this stage.
|
|
45
|
+
#
|
|
46
|
+
# A clean image rather than `FROM build`: the build stage's node_modules holds
|
|
47
|
+
# devDependencies.
|
|
48
|
+
# ==============================================================================
|
|
49
|
+
FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS deps
|
|
50
|
+
|
|
51
|
+
WORKDIR /usr/src/app
|
|
52
|
+
|
|
53
|
+
# Copy dependency manifests. `bunfig.toml` rides along so every install below
|
|
54
|
+
# passes its release-age gate and security scanner, as a local install does.
|
|
55
|
+
COPY package.json bun.lock bunfig.toml ./
|
|
56
|
+
|
|
57
|
+
# The scanner bunfig.toml names is a devDependency, and Bun installs a missing
|
|
58
|
+
# scanner through the same production-filtered install, which omits it and
|
|
59
|
+
# aborts. Seed it from the build stage's full install instead. Remove this line,
|
|
60
|
+
# and the `rm` at the end of this stage, if bunfig.toml stops naming a scanner.
|
|
61
|
+
COPY --from=build /usr/src/app/node_modules/@socketsecurity/bun-security-scanner ./node_modules/@socketsecurity/bun-security-scanner
|
|
62
|
+
|
|
63
|
+
# Docker names the target architecture `amd64`/`arm64`; Bun's `--cpu` takes
|
|
64
|
+
# `x64`/`arm64`. Mapped once here, read by both installs below. `oven/bun`
|
|
65
|
+
# publishes only these two architectures, so any other target fails here.
|
|
66
|
+
ARG TARGETOS
|
|
67
|
+
ARG TARGETARCH
|
|
68
|
+
RUN case "$TARGETARCH" in \
|
|
69
|
+
amd64) echo x64 ;; \
|
|
70
|
+
arm64) echo arm64 ;; \
|
|
71
|
+
*) echo "Unsupported TARGETARCH '$TARGETARCH': expected amd64 or arm64" >&2; exit 1 ;; \
|
|
72
|
+
esac > .bun-cpu
|
|
73
|
+
|
|
74
|
+
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
75
|
+
# that are not needed in the final production image.
|
|
76
|
+
# `--omit=peer` drops the framework's optional peer tiers (test runner, service
|
|
77
|
+
# SDKs, parsers) that Bun would otherwise auto-install. Anything this server
|
|
78
|
+
# actually imports belongs in its own `dependencies`, so nothing needed at
|
|
79
|
+
# runtime is lost.
|
|
80
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
81
|
+
bun install --production --omit=peer --frozen-lockfile --ignore-scripts \
|
|
82
|
+
--os="$TARGETOS" --cpu="$(cat .bun-cpu)"
|
|
83
|
+
|
|
84
|
+
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
85
|
+
# Installed by default. Omit them for a leaner image at build time
|
|
86
|
+
# with: docker build --build-arg OTEL_ENABLED=false
|
|
87
|
+
# The script reads the list and each range from the installed framework's
|
|
88
|
+
# `peerDependencies` and passes the target flags on to its `bun install`.
|
|
89
|
+
COPY scripts/install-otel.ts ./scripts/
|
|
90
|
+
ARG OTEL_ENABLED=true
|
|
91
|
+
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
92
|
+
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
93
|
+
bun scripts/install-otel.ts --os="$TARGETOS" --cpu="$(cat .bun-cpu)"; \
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# `--os`/`--cpu` have no libc counterpart, so a native dependency published in
|
|
97
|
+
# glibc and musl variants (DuckDB's bindings, for one) installs both. The
|
|
98
|
+
# runtime image is Debian (glibc) and never loads the musl copy; the script
|
|
99
|
+
# deletes every package whose own `libc` admits only musl. It follows every
|
|
100
|
+
# install, since a later `bun install` restores what it removes. A server on a
|
|
101
|
+
# musl (Alpine) runtime image drops these two lines.
|
|
102
|
+
COPY scripts/prune-musl-packages.ts ./scripts/
|
|
103
|
+
RUN bun scripts/prune-musl-packages.ts
|
|
104
|
+
|
|
105
|
+
# The seeded scanner served only the installs above; keep it out of the image.
|
|
106
|
+
RUN rm -rf node_modules/@socketsecurity/bun-security-scanner
|
|
107
|
+
|
|
108
|
+
|
|
30
109
|
# ==============================================================================
|
|
31
110
|
# Production Stage
|
|
32
111
|
#
|
|
33
112
|
# This stage creates a minimal, optimized, and secure image for running the
|
|
34
113
|
# application. It uses a slim base image and only includes production
|
|
35
|
-
# dependencies and build artifacts.
|
|
114
|
+
# dependencies and build artifacts. Its only Bun invocations are HEALTHCHECK
|
|
115
|
+
# and CMD, which run on the real target at container start.
|
|
36
116
|
# ==============================================================================
|
|
37
|
-
FROM oven/bun:1.4.
|
|
117
|
+
FROM oven/bun:1.4.2-slim AS production
|
|
38
118
|
|
|
39
119
|
WORKDIR /usr/src/app
|
|
40
120
|
|
|
41
|
-
# Set the environment to production for performance
|
|
42
|
-
# production dependencies are installed.
|
|
121
|
+
# Set the environment to production for performance.
|
|
43
122
|
ENV NODE_ENV=production
|
|
44
123
|
|
|
45
124
|
# OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
|
@@ -50,44 +129,37 @@ LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
|
50
129
|
LABEL org.opencontainers.image.version="${APP_VERSION}"
|
|
51
130
|
LABEL org.opencontainers.image.source="https://github.com/cyanheads/epa-mcp-server"
|
|
52
131
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# that are not needed in the final production image.
|
|
58
|
-
# `--omit=peer` drops the framework's optional peer tiers that Bun would
|
|
59
|
-
# otherwise auto-install. The OTEL step below carries the same flag so that
|
|
60
|
-
# install does not re-resolve the optional peer graph.
|
|
61
|
-
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
62
|
-
bun install --production --omit=peer --frozen-lockfile --ignore-scripts
|
|
63
|
-
|
|
64
|
-
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
65
|
-
# These are not bundled by default to keep the base image lean. Enable at build time
|
|
66
|
-
# with: docker build --build-arg OTEL_ENABLED=true
|
|
67
|
-
ARG OTEL_ENABLED=true
|
|
68
|
-
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
69
|
-
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
70
|
-
bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
|
|
71
|
-
@opentelemetry/instrumentation-http \
|
|
72
|
-
@opentelemetry/exporter-metrics-otlp-http \
|
|
73
|
-
@opentelemetry/exporter-trace-otlp-http \
|
|
74
|
-
@opentelemetry/instrumentation-pino \
|
|
75
|
-
@opentelemetry/resources \
|
|
76
|
-
@opentelemetry/sdk-metrics \
|
|
77
|
-
@opentelemetry/sdk-node \
|
|
78
|
-
@opentelemetry/sdk-trace-node \
|
|
79
|
-
@opentelemetry/semantic-conventions; \
|
|
80
|
-
fi
|
|
132
|
+
# The manifest comes from the build context: the deps stage's copy was rewritten
|
|
133
|
+
# by the OTel install, and the runtime reads only its name, version, and type.
|
|
134
|
+
COPY package.json ./
|
|
135
|
+
COPY --from=deps /usr/src/app/node_modules ./node_modules
|
|
81
136
|
|
|
82
137
|
# Copy the compiled application code from the build stage
|
|
83
138
|
COPY --from=build /usr/src/app/dist ./dist
|
|
84
139
|
|
|
140
|
+
# Mirror CLI (MirrorService adopters only — Tier 3, opt-in):
|
|
141
|
+
# Copy your mirror lifecycle scripts and emit a runtime tsconfig so Bun resolves
|
|
142
|
+
# the @/ path alias against ./dist/ rather than ./src/.
|
|
143
|
+
# See the api-mirror skill for the full recipe.
|
|
144
|
+
#
|
|
145
|
+
# COPY --from=build /usr/src/app/scripts/<your>-mirror-init.ts \
|
|
146
|
+
# /usr/src/app/scripts/<your>-mirror-refresh.ts \
|
|
147
|
+
# /usr/src/app/scripts/<your>-mirror-verify.ts \
|
|
148
|
+
# /usr/src/app/scripts/_mirror-context.ts \
|
|
149
|
+
# ./scripts/
|
|
150
|
+
# RUN echo '{"compilerOptions":{"baseUrl":".","paths":{"@/*":["./dist/*"]}}}' > tsconfig.json
|
|
151
|
+
|
|
85
152
|
# The 'oven/bun' image already provides a non-root user named 'bun'.
|
|
86
153
|
# We will use this existing user for enhanced security.
|
|
87
154
|
|
|
88
155
|
# Create and set permissions for the log directory, assigning ownership to the 'bun' user.
|
|
89
156
|
RUN mkdir -p /var/log/epa-mcp-server && chown -R bun:bun /var/log/epa-mcp-server
|
|
90
157
|
|
|
158
|
+
# Writable data dirs for on-disk SQLite stores (catalog index / observations
|
|
159
|
+
# mirror), owned by the runtime user. Mount a volume over either in production.
|
|
160
|
+
RUN mkdir -p /usr/src/app/.cache /usr/src/app/.mirror \
|
|
161
|
+
&& chown -R bun:bun /usr/src/app/.cache /usr/src/app/.mirror
|
|
162
|
+
|
|
91
163
|
# Switch to the non-root user
|
|
92
164
|
USER bun
|
|
93
165
|
|
|
@@ -103,7 +175,6 @@ ENV MCP_TRANSPORT_TYPE="http"
|
|
|
103
175
|
ENV MCP_SESSION_MODE="stateless"
|
|
104
176
|
ENV MCP_LOG_LEVEL="info"
|
|
105
177
|
ENV LOGS_DIR="/var/log/epa-mcp-server"
|
|
106
|
-
ENV MCP_FORCE_CONSOLE_LOGGING="true"
|
|
107
178
|
|
|
108
179
|
# Expose the port the server listens on
|
|
109
180
|
EXPOSE ${MCP_HTTP_PORT}
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>@cyanheads/epa-mcp-server</h1>
|
|
3
3
|
<p><b>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.</b>
|
|
4
|
-
<div>9 Tools • 2 Resources</div>
|
|
5
4
|
</p>
|
|
5
|
+
<div>9 Tools • 2 Resources</div>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/epa-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/epa-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -21,151 +21,131 @@
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Overview
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
EPA environmental data across five federal programs — facility compliance (ECHO), toxic chemical releases (TRI), Superfund cleanup sites, drinking water systems (SDWIS), and environmental-justice screening (EJScreen) — plus real-time air quality via AirNow. Search facilities by location or compliance status, pull inspection and enforcement history, track toxic releases across a region, and screen a point for environmental-justice risk from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
|
|
27
|
+
|
|
28
|
+
### Tools
|
|
27
29
|
|
|
28
30
|
| Tool | Description |
|
|
29
31
|
|:---|:---|
|
|
30
|
-
| `epa_search_facilities` | Search EPA-regulated facilities by location
|
|
31
|
-
| `epa_get_facility` |
|
|
32
|
-
| `epa_search_violations` | Search EPA civil and criminal enforcement cases by state,
|
|
33
|
-
| `epa_get_air_quality` |
|
|
34
|
-
| `epa_get_tri_releases` |
|
|
35
|
-
| `epa_search_tri_releases` |
|
|
36
|
-
| `epa_search_superfund` | Search Superfund (CERCLA/SEMS) sites by location or NPL listing status
|
|
37
|
-
| `epa_search_water_systems` | Search drinking water systems (SDWIS) by state or ZIP code
|
|
38
|
-
| `epa_get_ejscreen` |
|
|
39
|
-
|
|
40
|
-
###
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- Compliance filter: `has_violation` flag to surface only non-compliant facilities
|
|
47
|
-
- Returns `RegistryID` (key for `epa_get_facility`), `FacFIPSCode` (county FIPS for Census chaining), and coordinates
|
|
48
|
-
- Results cap enforced — unscoped searches are prohibited at the input validation layer
|
|
32
|
+
| `epa_search_facilities` | Search EPA-regulated facilities by location, industry program, or compliance status across CAA, CWA, RCRA, TRI, and SDWA |
|
|
33
|
+
| `epa_get_facility` | Full compliance profile for one facility by EPA Registry ID — inspections, enforcement actions, and penalties |
|
|
34
|
+
| `epa_search_violations` | Search EPA civil and criminal enforcement cases by state, program, or date range |
|
|
35
|
+
| `epa_get_air_quality` | Current AQI observations or next-day forecasts from AirNow |
|
|
36
|
+
| `epa_get_tri_releases` | Per-chemical Toxic Release Inventory data for a single facility |
|
|
37
|
+
| `epa_search_tri_releases` | Toxic Release Inventory records across facilities in a state or county |
|
|
38
|
+
| `epa_search_superfund` | Search Superfund (CERCLA/SEMS) sites by location or NPL listing status |
|
|
39
|
+
| `epa_search_water_systems` | Search drinking water systems (SDWIS) by state or ZIP code |
|
|
40
|
+
| `epa_get_ejscreen` | EJScreen environmental-justice indicators for a point and buffer |
|
|
41
|
+
|
|
42
|
+
### Resources
|
|
43
|
+
|
|
44
|
+
| Resource | Description |
|
|
45
|
+
|:---|:---|
|
|
46
|
+
| `epa://facility/{registry_id}` | Full compliance profile for a facility by EPA Registry ID (same data as `epa_get_facility`) |
|
|
47
|
+
| `epa://superfund/{site_id}` | Superfund site record by SEMS site ID |
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
All resource data is also reachable via tools — use `epa_get_facility` and `epa_search_superfund` for programmatic access in tool-only MCP clients.
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
## Capability reference
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
### `epa_search_facilities` <sub>tool</sub>
|
|
55
54
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
55
|
+
- Provide ZIP code, state, city (preferably with state), or latitude + longitude + radius_miles (max 100 miles); up to 100 results (default 50)
|
|
56
|
+
- Returns `registryId` for `epa_get_facility`, plus `fipsCode` when available for Census queries
|
|
57
|
+
- `programs` filters CAA, CWA, RCRA, TRI, or SDWA; `has_violation` filters significant violations
|
|
59
58
|
|
|
60
59
|
---
|
|
61
60
|
|
|
62
|
-
### `
|
|
63
|
-
|
|
64
|
-
Search area-level EPA enforcement cases — distinct from per-facility history in `epa_get_facility`.
|
|
61
|
+
### `epa_get_facility` <sub>tool</sub>
|
|
65
62
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
- Returns case identifier, affected facility name and Registry ID for downstream `epa_get_facility` lookup
|
|
63
|
+
- `registry_id` comes from `epa_search_facilities`
|
|
64
|
+
- Returns compliance, inspections, enforcement actions, penalties, and TRI totals; available program data survives partial upstream failures
|
|
65
|
+
- `airCompliance` / `waterCompliance` appear only for registered programs; `facility_not_found` identifies a missing record
|
|
70
66
|
|
|
71
67
|
---
|
|
72
68
|
|
|
73
|
-
### `
|
|
69
|
+
### `epa_search_violations` <sub>tool</sub>
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
- `mode: current` returns the latest observed AQI per pollutant; `mode: forecast` returns daily AQI forecasts (requires `forecast_date`)
|
|
79
|
-
- AQI categories: Good (1) through Hazardous (6) with numeric and text category
|
|
80
|
-
- Data is preliminary — suitable for awareness, not regulatory or enforcement decisions
|
|
81
|
-
- AirNow responses cached at ~1 hour TTL to respect rate limits
|
|
71
|
+
- Requires `state` or `zip_code`; up to 100 cases (default 50)
|
|
72
|
+
- `program` filters CAA, CWA, RCRA, SDWA, CERCLA, FIFRA, or TSCA; `case_type` is civil, criminal, or all (default); `date_filed_start` / `date_filed_end` filter ISO dates
|
|
73
|
+
- `facilityName` and `registryId` are unavailable from this endpoint; discover facilities with `epa_search_facilities`
|
|
82
74
|
|
|
83
75
|
---
|
|
84
76
|
|
|
85
|
-
### `
|
|
86
|
-
|
|
87
|
-
Query per-chemical release breakdown for a single TRI facility.
|
|
77
|
+
### `epa_get_air_quality` <sub>tool</sub>
|
|
88
78
|
|
|
89
|
-
-
|
|
90
|
-
- Returns
|
|
91
|
-
-
|
|
92
|
-
- TRI data lags ~18 months — most recent available year is typically 2 years prior to current
|
|
79
|
+
- Provide `zip_code` or both `latitude` and `longitude`; `mode` is current (default) or forecast (requires ISO `forecast_date`); `distance_miles` defaults to 25, max 300
|
|
80
|
+
- Returns per-pollutant AQI, `categoryNumber` (1 Good–6 Hazardous), and `categoryName`; preliminary data is unsuitable for regulatory, trend, or enforcement decisions
|
|
81
|
+
- Registered only when `AIRNOW_API_KEY` is set; responses cache for about one hour
|
|
93
82
|
|
|
94
83
|
---
|
|
95
84
|
|
|
96
|
-
### `
|
|
85
|
+
### `epa_get_tri_releases` <sub>tool</sub>
|
|
97
86
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- State and optional county scope; required year parameter
|
|
101
|
-
- Optional chemical name filter to focus on a specific substance
|
|
102
|
-
- Returns facility coordinates for downstream map or proximity analysis
|
|
103
|
-
- Complement to `epa_get_tri_releases` — use this for area discovery, then drill into a specific facility
|
|
87
|
+
- `facility_id` is the TRI `facilityId` from `epa_search_tri_releases`; optional `year` (1987–2030, all available by default) and partial `chemical_name`
|
|
88
|
+
- Returns per-chemical air, water, land, and underground-injection releases, plus a separate one-time/non-routine total
|
|
104
89
|
|
|
105
90
|
---
|
|
106
91
|
|
|
107
|
-
### `
|
|
108
|
-
|
|
109
|
-
Search Superfund (CERCLA/SEMS) sites by location or proximity.
|
|
92
|
+
### `epa_search_tri_releases` <sub>tool</sub>
|
|
110
93
|
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- Returns site cleanup status and coordinates for downstream spatial analysis
|
|
94
|
+
- Requires 2-letter `state`; optional partial `county`, `chemical_name`, and `year`; up to 200 records (default 50)
|
|
95
|
+
- Returns `facilityId` for `epa_get_tri_releases`; enrichment marks results truncated when the limit is reached
|
|
114
96
|
|
|
115
97
|
---
|
|
116
98
|
|
|
117
|
-
### `
|
|
99
|
+
### `epa_search_superfund` <sub>tool</sub>
|
|
118
100
|
|
|
119
|
-
|
|
101
|
+
- Provide `state`/`city`/`zip_code`, or `latitude` + `longitude` + `radius_miles` (0.1–500); up to 200 sites (default 50)
|
|
102
|
+
- Returns site identifiers, location, NPL, and cleanup status; `npl_status` filters listed, not-listed, proposed, or all (default)
|
|
103
|
+
|
|
104
|
+
---
|
|
120
105
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
-
|
|
106
|
+
### `epa_search_water_systems` <sub>tool</sub>
|
|
107
|
+
|
|
108
|
+
- Requires `state` or `zip_code`; up to 200 systems (default 50)
|
|
109
|
+
- `has_violation` filters active violations; `pws_type` is community, non-transient, or transient, returned as CWS / NTNCWS / TNCWS in `type`
|
|
124
110
|
|
|
125
111
|
---
|
|
126
112
|
|
|
127
|
-
### `epa_get_ejscreen`
|
|
113
|
+
### `epa_get_ejscreen` <sub>tool</sub>
|
|
128
114
|
|
|
129
|
-
|
|
115
|
+
- Requires `latitude` and `longitude`; `distance` defaults to 1, `unit` to miles (kilometers accepted); buffer capped at 15 miles
|
|
116
|
+
- Returns 13 environmental and 6 demographic indicators with national/state percentiles and demographic indices; outside coverage, `coverage.valid: false` carries a note
|
|
117
|
+
- EJScreen v2.2 (2022) comes from the community-maintained EJAM API (Public Environmental Data Partners)
|
|
130
118
|
|
|
131
|
-
|
|
132
|
-
- 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, RSEI toxic air releases) and 6 demographic indicators (people of color, low income, limited English, less than high school, under 5, over 64)
|
|
133
|
-
- Each indicator carries national and state percentiles plus EJ Index values; the Demographic Index and Supplemental Demographic Index are included
|
|
134
|
-
- Points outside US coverage return a coverage note instead of fabricated indicators
|
|
135
|
-
- **Data source:** EJScreen v2.2 (2022) served via the community-maintained [EJAM API](https://api.ejanalysis.com) (Public Environmental Data Partners), which rehosts EJScreen after EPA discontinued public access in 2025 — not a live EPA endpoint
|
|
119
|
+
---
|
|
136
120
|
|
|
137
|
-
|
|
121
|
+
### `epa://facility/{registry_id}` <sub>resource</sub>
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| Resource | `epa://facility/{registry_id}` | Full compliance profile for a facility by EPA Registry ID (same data as `epa_get_facility`) |
|
|
142
|
-
| Resource | `epa://superfund/{site_id}` | Superfund site record by SEMS site ID |
|
|
123
|
+
- `registry_id` comes from `epa_search_facilities`
|
|
124
|
+
- Returns the `epa_get_facility` compliance profile; errors when no facility resolves
|
|
143
125
|
|
|
144
|
-
|
|
126
|
+
---
|
|
145
127
|
|
|
146
|
-
|
|
128
|
+
### `epa://superfund/{site_id}` <sub>resource</sub>
|
|
147
129
|
|
|
148
|
-
|
|
130
|
+
- `site_id` comes from `epa_search_superfund`
|
|
131
|
+
- Returns the Superfund site record; errors when no SEMS record resolves
|
|
132
|
+
|
|
133
|
+
## Features
|
|
149
134
|
|
|
150
|
-
-
|
|
151
|
-
- Unified error handling — handlers throw, framework catches, classifies, and formats
|
|
152
|
-
- Pluggable auth: `none`, `jwt`, `oauth`
|
|
153
|
-
- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
|
|
154
|
-
- Structured logging with optional OpenTelemetry tracing
|
|
155
|
-
- STDIO and Streamable HTTP transports
|
|
135
|
+
Built on [`@cyanheads/mcp-ts-core`](https://github.com/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.
|
|
156
136
|
|
|
157
137
|
EPA-specific:
|
|
158
138
|
|
|
159
|
-
- Multiple environmental data sources unified behind a single `epa_` tool surface: ECHO (facility compliance), Envirofacts DMAP (TRI, Superfund, SDWIS), AirNow (real-time air quality), and the community-maintained EJAM API rehosting EJScreen
|
|
139
|
+
- Multiple environmental data sources unified behind a single `epa_` tool surface: ECHO (facility compliance), Envirofacts DMAP (TRI, Superfund, SDWIS), AirNow (real-time air quality), and the community-maintained EJAM API rehosting EJScreen data (v2.2, 2022)
|
|
160
140
|
- Parallel ECHO DFR aggregation in `epa_get_facility` — 3–5 upstream calls resolved concurrently with `Promise.allSettled`
|
|
161
141
|
- AirNow response caching (~1 hour TTL) to stay within per-key rate limits
|
|
162
|
-
-
|
|
142
|
+
- TRI reporting lags about 18 months; the latest available reporting year is typically two years behind
|
|
163
143
|
|
|
164
144
|
Agent-friendly output:
|
|
165
145
|
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
146
|
+
- Facility search supplies `registryId` for compliance lookups and `fipsCode` when available for Census queries; TRI search supplies `facilityId` for release details
|
|
147
|
+
- Structured partial failure — `epa_get_facility` returns available program data even when one DFR endpoint is unavailable, with `airCompliance`/`waterCompliance` present only when that program applies
|
|
148
|
+
- Recovery-hint messages on empty results — every search tool returns a `message` field that echoes the applied filters and suggests how to broaden the search
|
|
169
149
|
|
|
170
150
|
## Getting started
|
|
171
151
|
|
|
@@ -207,6 +187,25 @@ Or with npx (no Bun required):
|
|
|
207
187
|
}
|
|
208
188
|
```
|
|
209
189
|
|
|
190
|
+
Or with Docker:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"mcpServers": {
|
|
195
|
+
"epa-mcp-server": {
|
|
196
|
+
"type": "stdio",
|
|
197
|
+
"command": "docker",
|
|
198
|
+
"args": [
|
|
199
|
+
"run", "-i", "--rm",
|
|
200
|
+
"-e", "MCP_TRANSPORT_TYPE=stdio",
|
|
201
|
+
"-e", "AIRNOW_API_KEY=your-airnow-key",
|
|
202
|
+
"ghcr.io/cyanheads/epa-mcp-server:latest"
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
210
209
|
For Streamable HTTP, set the transport and start the server:
|
|
211
210
|
|
|
212
211
|
```sh
|
|
@@ -216,7 +215,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 AIRNOW_API_KEY=... bun run start:http
|
|
|
216
215
|
|
|
217
216
|
### Prerequisites
|
|
218
217
|
|
|
219
|
-
- [Bun v1.
|
|
218
|
+
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+); development and Docker use Bun v1.4.2.
|
|
220
219
|
- (Optional) An AirNow API key to enable `epa_get_air_quality` — register free at [docs.airnowapi.org/account/request](https://docs.airnowapi.org/account/request/). Without it the server runs the other 8 tools. ECHO, DMAP, and EJScreen tools require no API key.
|
|
221
220
|
|
|
222
221
|
### Installation
|
|
@@ -260,11 +259,17 @@ All configuration is validated at startup via Zod schemas in `src/config/`. Key
|
|
|
260
259
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |
|
|
261
260
|
| `MCP_HTTP_PORT` | HTTP server port | `3010` |
|
|
262
261
|
| `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path | `/mcp` |
|
|
262
|
+
| `MCP_SESSION_MODE` | HTTP sessions: `auto`, `stateful`, or `stateless`. Explicit environment values override the server default; `auto` resolves to `stateful`. Tenant-scoped caching is independent of sessions. | `stateless` |
|
|
263
263
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth` | `none` |
|
|
264
264
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424) | `info` |
|
|
265
|
+
| `LOG_TOOL_FAILURE_PAYLOADS` | Log failed calls' arguments and results with key-name redaction. Secrets inside free-form values remain visible. | `false` |
|
|
266
|
+
| `LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES` | UTF-8 byte cap per logged payload. | `16384` |
|
|
265
267
|
| `LOGS_DIR` | Directory for log files (Node.js only) | `<project-root>/logs` |
|
|
266
268
|
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1` | `in-memory` |
|
|
267
269
|
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) tracing and metrics | `false` |
|
|
270
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP base URL; traces use `/v1/traces`, metrics `/v1/metrics`. | — |
|
|
271
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` / `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Signal-specific endpoint overrides, used as-is. | — |
|
|
272
|
+
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Enables OTLP log export; the base endpoint alone does not enable it. | — |
|
|
268
273
|
|
|
269
274
|
See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
270
275
|
|
|
@@ -327,7 +332,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
327
332
|
|
|
328
333
|
## Contributing
|
|
329
334
|
|
|
330
|
-
Issues
|
|
335
|
+
Issues are welcome. Run checks and tests before submitting:
|
|
331
336
|
|
|
332
337
|
```sh
|
|
333
338
|
bun run devcheck
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Stateless HTTP defaults, private upstream errors, and mcp-ts-core 0.13.6 adoption."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: true
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Bun 1.4 or later is required. Development workflows now live under
|
|
7
|
+
framework-skills/ rather than skills/.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 0.3.2 — 2026-09-19
|
|
11
|
+
|
|
12
|
+
## Changed
|
|
13
|
+
|
|
14
|
+
- HTTP defaults to stateless when MCP_SESSION_MODE is unset; an explicit environment value still overrides it (#22).
|
|
15
|
+
- **Environment configuration** treats empty and whole-placeholder values as unset through `@cyanheads/mcp-ts-core`, so optional AirNow configuration stays disabled and the EJScreen endpoint uses its default.
|
|
16
|
+
- **Runtime baseline** now requires Bun 1.4 or later; this is a runtime migration with unchanged tool contracts.
|
|
17
|
+
- **Tool arguments** accept case-style aliases for declared keys and ignore the framework's fixed client metadata-key list; other unknown keys still return `InvalidParams`.
|
|
18
|
+
- **Error responses** include reason and retryability terms when available and preserve recovery guidance for client-side branching.
|
|
19
|
+
- **Development workflows** moved from `skills/` to `framework-skills/`, and package, bundle, and manifest checks follow the current framework delivery rules.
|
|
20
|
+
- **Project documentation and issue forms** now describe the resource surface, reporting route, security advisory path, and current runtime values.
|
|
21
|
+
|
|
22
|
+
## Fixed
|
|
23
|
+
|
|
24
|
+
- **AirNow, ECHO, DMAP, and EJAM failures** no longer expose upstream request URLs in MCP error data. [#23](https://github.com/cyanheads/epa-mcp-server/issues/23)
|
|
25
|
+
- **Invalid tool arguments** now use the framework's production `InvalidParams` error envelope with structured error content.
|
|
26
|
+
- **EJAM non-400 HTTP failures** now use the framework's upstream HTTP classification, while invalid coordinates retain their validation error.
|
|
27
|
+
|
|
28
|
+
## Security
|
|
29
|
+
|
|
30
|
+
- **AirNow error handling** no longer returns a credential-bearing upstream URL to clients. [#23](https://github.com/cyanheads/epa-mcp-server/issues/23)
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
- `@cyanheads/mcp-ts-core` `^0.12.3` → `^0.13.6`
|
|
35
|
+
- `zod` `^4.4.3` → `^4.6.5`
|
|
36
|
+
- `@biomejs/biome` `^2.5.9` → `^2.5.14`
|
|
37
|
+
- `@types/node` `^26.2.0` → `^26.6.1`
|
|
38
|
+
- `ignore` `^7.0.6` → `^7.0.9`
|
|
39
|
+
- `tsc-alias` `^1.9.2` → `^1.9.5`
|
|
40
|
+
- `vitest` `^4.1.11` → `^5.0.1`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "ECHO debug logs use endpoint labels instead of request URLs, with framework recovery and error privacy updates."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.3.3 — 2026-10-07
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **Domain recovery** uses the framework's declared-hint fill from 0.13.10; redundant forwards are removed and contract tests preserve hints on both MCP result surfaces.
|
|
12
|
+
- **Framework input and errors** adopt 0.13.9's integer-to-string repair and field-path guidance, 0.13.10's error request IDs, 0.13.12's generated IDs and URL-origin redaction, and 0.13.13's removal of framework-added `rootCause` from error envelopes.
|
|
13
|
+
- **Docker builds** use Bun 1.4.2 and a native build-platform dependency stage, cross-installing for amd64/arm64 with framework-range OTel peers and pruning musl-only bindings.
|
|
14
|
+
- **Framework tooling and guides** are synchronized; configuration documents OTLP endpoints and opt-in failed-call payload logging, packaging excludes `.mcpb`, cache, and logs, and CodeQL uploads per-language categories.
|
|
15
|
+
- **ECHO User-Agent** reports the current server version.
|
|
16
|
+
|
|
17
|
+
## Fixed
|
|
18
|
+
|
|
19
|
+
- **MCP Registry npm entries** launch with `npx`, drop npm-script arguments, and fix `MCP_TRANSPORT_TYPE=http` for the HTTP entry.
|
|
20
|
+
|
|
21
|
+
## Security
|
|
22
|
+
|
|
23
|
+
- **ECHO debug logs** identify facility and enforcement requests by fixed endpoint labels and query IDs, omitting full configured URL paths and query strings ([#26](https://github.com/cyanheads/epa-mcp-server/issues/26)).
|
|
24
|
+
|
|
25
|
+
## Dependencies
|
|
26
|
+
|
|
27
|
+
- `@cyanheads/mcp-ts-core` ^0.13.6 → ^0.13.13.
|
|
28
|
+
- Dev: `@biomejs/biome` ^2.5.14 → ^2.5.15.
|
|
29
|
+
- Dev: `@socketsecurity/bun-security-scanner` ^1.1.2 → ^1.1.3.
|
|
30
|
+
- Dev: `@types/node` ^26.6.1 → ^26.6.4.
|
|
31
|
+
- Dev: `ignore` ^7.0.9 → ^7.0.12.
|
|
32
|
+
- Dev: `tsc-alias` ^1.9.5 → ^1.9.7.
|
|
33
|
+
- Dev: `vitest` ^5.0.1 → ^5.0.3.
|
|
34
|
+
- **Bun** 1.4.0 → 1.4.2 in `packageManager` and Docker base images.
|
package/changelog/template.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
# Required. One-line GitHub Release-style headline. 350 character cap — a
|
|
8
8
|
# ceiling, not a target. Default short and scannable. Don't pad, don't stitch
|
|
9
|
-
# unrelated changes with commas/semicolons into an inventory — pick the
|
|
10
|
-
# headline
|
|
9
|
+
# unrelated changes with commas/semicolons into an inventory — pick the one
|
|
10
|
+
# headline the release is about. Quotes required: unquoted YAML treats
|
|
11
11
|
# `: ` inside the value as a key separator and fails GitHub's strict parser.
|
|
12
12
|
summary: ""
|
|
13
13
|
|
|
@@ -117,30 +117,13 @@ security: false
|
|
|
117
117
|
in that unrelated item's metadata.
|
|
118
118
|
|
|
119
119
|
TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
|
|
120
|
-
via `gh release create --notes-from-tag`.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
Dependency bumps: ← section header
|
|
128
|
-
← blank line
|
|
129
|
-
- `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
|
|
130
|
-
← blank line
|
|
131
|
-
Changed: ← only sections with entries
|
|
132
|
-
← blank line
|
|
133
|
-
- `format()` output includes `query` in text mode
|
|
134
|
-
← blank line
|
|
135
|
-
Added:
|
|
136
|
-
← blank line
|
|
137
|
-
- `manifest.json` scaffolded for MCPB bundle support
|
|
138
|
-
- Install badges (Claude Desktop, Cursor, VS Code)
|
|
139
|
-
← blank line
|
|
140
|
-
<N> tests pass; `bun run devcheck` clean. ← footer
|
|
141
|
-
|
|
142
|
-
Never a flat comma-separated string. Always structured markdown with
|
|
143
|
-
sections. The tag must scan well as a rendered GitHub Release page.
|
|
120
|
+
via `gh release create --notes-from-tag`. It is a condensed digest of this
|
|
121
|
+
entry, never a copy, and its format is owned by the `release-and-publish`
|
|
122
|
+
skill (step 4, "Create the annotated tag"): a short subject line without the
|
|
123
|
+
version, flat headline bullets — no Keep-a-Changelog section headers, no
|
|
124
|
+
gates line — at most one deps line, issue backlinks, and the changelog link
|
|
125
|
+
last. In release-PR mode the `git-wrapup` skill authors those bullets as the
|
|
126
|
+
PR body's `## Changes` and the tag copies them.
|
|
144
127
|
-->
|
|
145
128
|
|
|
146
129
|
## Added
|