@cyanheads/epa-mcp-server 0.3.0 → 0.3.2
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 +413 -0
- package/CLAUDE.md +91 -77
- package/Dockerfile +14 -7
- package/LICENSE +1 -1
- package/README.md +109 -97
- package/changelog/0.3.x/0.3.1.md +27 -0
- package/changelog/0.3.x/0.3.2.md +40 -0
- package/changelog/template.md +67 -43
- 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-ejscreen.tool.d.ts +2 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js +1 -0
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +12 -20
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts +1 -6
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js +0 -6
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-violations.tool.d.ts +3 -3
- 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 +57 -41
- 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 +32 -10
- package/dist/services/echo/echo-service.js.map +1 -1
- package/dist/services/echo/types.d.ts +1 -0
- package/dist/services/echo/types.d.ts.map +1 -1
- package/dist/services/ejscreen/ejscreen-service.d.ts.map +1 -1
- package/dist/services/ejscreen/ejscreen-service.js +6 -8
- package/dist/services/ejscreen/ejscreen-service.js.map +1 -1
- package/package.json +16 -14
- package/server.json +4 -4
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>@cyanheads/epa-mcp-server</h1>
|
|
3
|
-
<p><b>
|
|
4
|
-
<div>9 Tools • 2 Resources</div>
|
|
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>
|
|
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,143 @@
|
|
|
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
|
+
- Geographic filters: ZIP code, state, city (pair with state), or latitude + longitude + radius_miles (max 100 miles) for proximity search — at least one is required
|
|
56
|
+
- Optional `programs` filter narrows to CAA, CWA, RCRA, TRI, or SDWA registrants; `has_violation` surfaces only non-compliant facilities
|
|
57
|
+
- Returns `registryId` for `epa_get_facility`, plus `fipsCode` when available for Census chaining
|
|
58
|
+
- Up to 100 results per call (default 50)
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
### `
|
|
63
|
-
|
|
64
|
-
Search area-level EPA enforcement cases — distinct from per-facility history in `epa_get_facility`.
|
|
62
|
+
### `epa_get_facility` <sub>tool</sub>
|
|
65
63
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
64
|
+
- Input: `registry_id`, obtained from `epa_search_facilities`
|
|
65
|
+
- Aggregates 3–5 ECHO DFR endpoints in parallel — program flags and TRI totals, compliance summary, inspection/enforcement history, CAA details, CWA/NPDES permit details
|
|
66
|
+
- Uses `Promise.allSettled` — partial data is returned even when one upstream endpoint fails
|
|
67
|
+
- `airCompliance` / `waterCompliance` are present only when the facility is registered under that program
|
|
68
|
+
- `facility_not_found` when ECHO has no record for the Registry ID
|
|
70
69
|
|
|
71
70
|
---
|
|
72
71
|
|
|
73
|
-
### `
|
|
72
|
+
### `epa_search_violations` <sub>tool</sub>
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
- `
|
|
79
|
-
-
|
|
80
|
-
- Data is preliminary — suitable for awareness, not regulatory or enforcement decisions
|
|
81
|
-
- AirNow responses cached at ~1 hour TTL to respect rate limits
|
|
74
|
+
- At least one of `state` or `zip_code` is required
|
|
75
|
+
- `program` filter covers CAA, CWA, RCRA, SDWA, CERCLA, FIFRA, or TSCA; `case_type` is civil, criminal, or all (default all)
|
|
76
|
+
- Date range filter by filing date (ISO 8601 `date_filed_start` / `date_filed_end`)
|
|
77
|
+
- `facilityName` and `registryId` are not populated by ECHO's enforcement-case endpoint — chain into `epa_get_facility` for facility detail
|
|
78
|
+
- Up to 100 cases per call (default 50)
|
|
82
79
|
|
|
83
80
|
---
|
|
84
81
|
|
|
85
|
-
### `
|
|
86
|
-
|
|
87
|
-
Query per-chemical release breakdown for a single TRI facility.
|
|
82
|
+
### `epa_get_air_quality` <sub>tool</sub>
|
|
88
83
|
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
84
|
+
- Provide `zip_code` or both `latitude` and `longitude`
|
|
85
|
+
- `mode`: `current` (default) or `forecast` (requires `forecast_date`, ISO 8601)
|
|
86
|
+
- Per-pollutant AQI (PM2.5, ozone, CO, SO2, NO2) with numeric `categoryNumber` (1 Good – 6 Hazardous) and `categoryName`
|
|
87
|
+
- `distance_miles` sets the reporting-station search radius (default 25, max 300)
|
|
88
|
+
- Data is preliminary — informational use only, not for regulatory decisions; responses are cached ~1 hour
|
|
89
|
+
- Only registered when `AIRNOW_API_KEY` is set
|
|
93
90
|
|
|
94
91
|
---
|
|
95
92
|
|
|
96
|
-
### `
|
|
93
|
+
### `epa_get_tri_releases` <sub>tool</sub>
|
|
97
94
|
|
|
98
|
-
|
|
95
|
+
- `facility_id` is the TRI `facilityId` from `epa_search_tri_releases`; optional `year` (1987–2030, defaults to all available years) and `chemical_name` (partial match)
|
|
96
|
+
- Per-chemical breakdown by medium — air, water, land, underground injection — plus a separate one-time/non-routine release total
|
|
97
|
+
- TRI data lags ~18 months; the most recent available year is typically 2 years prior to the current year
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### `epa_search_tri_releases` <sub>tool</sub>
|
|
102
|
+
|
|
103
|
+
- `state` is required (2-letter); optional `county` (partial match), `year`, and `chemical_name`
|
|
104
|
+
- Up to 200 records per call (default 50); an enrichment flag marks the result truncated when it hits the limit
|
|
103
105
|
- Complement to `epa_get_tri_releases` — use this for area discovery, then drill into a specific facility
|
|
104
106
|
|
|
105
107
|
---
|
|
106
108
|
|
|
107
|
-
### `epa_search_superfund`
|
|
108
|
-
|
|
109
|
-
Search Superfund (CERCLA/SEMS) sites by location or proximity.
|
|
109
|
+
### `epa_search_superfund` <sub>tool</sub>
|
|
110
110
|
|
|
111
|
-
- Two input shapes: state
|
|
112
|
-
-
|
|
113
|
-
-
|
|
111
|
+
- Two input shapes: `state`/`city`/`zip_code`, or `latitude`+`longitude`+`radius_miles` (0.1–500 miles) — one is required
|
|
112
|
+
- `npl_status` filter: `listed`, `not-listed`, `proposed`, or `all` (default `all`)
|
|
113
|
+
- Up to 200 sites per call (default 50)
|
|
114
114
|
|
|
115
115
|
---
|
|
116
116
|
|
|
117
|
-
### `epa_search_water_systems`
|
|
117
|
+
### `epa_search_water_systems` <sub>tool</sub>
|
|
118
118
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
-
|
|
122
|
-
- `has_violation` flag surfaces only systems with current violations
|
|
123
|
-
- PWS type filter: community (`CWS`), non-transient non-community (`NTNCWS`), or transient non-community (`TNCWS`)
|
|
119
|
+
- At least one of `state` or `zip_code` is required
|
|
120
|
+
- `has_violation` surfaces only systems with active violations; `pws_type` filters to `community`, `non-transient`, or `transient` (output `type` reports the SDWIS codes CWS / NTNCWS / TNCWS)
|
|
121
|
+
- Up to 200 systems per call (default 50)
|
|
124
122
|
|
|
125
123
|
---
|
|
126
124
|
|
|
127
|
-
### `epa_get_ejscreen`
|
|
125
|
+
### `epa_get_ejscreen` <sub>tool</sub>
|
|
128
126
|
|
|
129
|
-
|
|
127
|
+
- Input: `latitude`, `longitude`, `distance` (default 1), and `unit` (`miles` or `kilometers`, default `miles`); kilometers are converted to miles before the request, and the buffer is capped at 15 miles
|
|
128
|
+
- Returns 13 environmental and 6 demographic indicators, each with national/state percentiles, plus the Demographic Index and Supplemental Demographic Index
|
|
129
|
+
- Points outside US coverage return `coverage.valid: false` with a note instead of fabricated indicators
|
|
130
|
+
- Data source: EJScreen v2.2 (2022) via the community-maintained EJAM API (Public Environmental Data Partners) — not a live EPA endpoint
|
|
130
131
|
|
|
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
|
|
132
|
+
---
|
|
136
133
|
|
|
137
|
-
|
|
134
|
+
### `epa://facility/{registry_id}` <sub>resource</sub>
|
|
138
135
|
|
|
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 |
|
|
136
|
+
- Same data as `epa_get_facility`; `registry_id` comes from `epa_search_facilities`
|
|
137
|
+
- Errors when the Registry ID has no ECHO record
|
|
143
138
|
|
|
144
|
-
|
|
139
|
+
---
|
|
145
140
|
|
|
146
|
-
|
|
141
|
+
### `epa://superfund/{site_id}` <sub>resource</sub>
|
|
147
142
|
|
|
148
|
-
|
|
143
|
+
- Same data as `epa_search_superfund` records; `site_id` comes from `epa_search_superfund`
|
|
144
|
+
- Errors when the site ID has no SEMS record
|
|
145
|
+
|
|
146
|
+
## Features
|
|
149
147
|
|
|
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
|
|
148
|
+
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
149
|
|
|
157
150
|
EPA-specific:
|
|
158
151
|
|
|
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
|
|
152
|
+
- 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
153
|
- Parallel ECHO DFR aggregation in `epa_get_facility` — 3–5 upstream calls resolved concurrently with `Promise.allSettled`
|
|
161
154
|
- AirNow response caching (~1 hour TTL) to stay within per-key rate limits
|
|
162
|
-
- DMAP coordinate normalization — `tri.tri_facility` DDMMSS integers converted to decimal degrees
|
|
163
155
|
|
|
164
156
|
Agent-friendly output:
|
|
165
157
|
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
158
|
+
- Facility search supplies `registryId` for compliance lookups and `fipsCode` when available for Census queries; TRI search supplies `facilityId` for release details
|
|
159
|
+
- 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
|
|
160
|
+
- 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
161
|
|
|
170
162
|
## Getting started
|
|
171
163
|
|
|
@@ -207,6 +199,25 @@ Or with npx (no Bun required):
|
|
|
207
199
|
}
|
|
208
200
|
```
|
|
209
201
|
|
|
202
|
+
Or with Docker:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"mcpServers": {
|
|
207
|
+
"epa-mcp-server": {
|
|
208
|
+
"type": "stdio",
|
|
209
|
+
"command": "docker",
|
|
210
|
+
"args": [
|
|
211
|
+
"run", "-i", "--rm",
|
|
212
|
+
"-e", "MCP_TRANSPORT_TYPE=stdio",
|
|
213
|
+
"-e", "AIRNOW_API_KEY=your-airnow-key",
|
|
214
|
+
"ghcr.io/cyanheads/epa-mcp-server:latest"
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
210
221
|
For Streamable HTTP, set the transport and start the server:
|
|
211
222
|
|
|
212
223
|
```sh
|
|
@@ -216,7 +227,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 AIRNOW_API_KEY=... bun run start:http
|
|
|
216
227
|
|
|
217
228
|
### Prerequisites
|
|
218
229
|
|
|
219
|
-
- [Bun v1.
|
|
230
|
+
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
220
231
|
- (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
232
|
|
|
222
233
|
### Installation
|
|
@@ -260,6 +271,7 @@ All configuration is validated at startup via Zod schemas in `src/config/`. Key
|
|
|
260
271
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |
|
|
261
272
|
| `MCP_HTTP_PORT` | HTTP server port | `3010` |
|
|
262
273
|
| `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path | `/mcp` |
|
|
274
|
+
| `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
275
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth` | `none` |
|
|
264
276
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424) | `info` |
|
|
265
277
|
| `LOGS_DIR` | Directory for log files (Node.js only) | `<project-root>/logs` |
|
|
@@ -327,7 +339,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
327
339
|
|
|
328
340
|
## Contributing
|
|
329
341
|
|
|
330
|
-
Issues
|
|
342
|
+
Issues are welcome. Run checks and tests before submitting:
|
|
331
343
|
|
|
332
344
|
```sh
|
|
333
345
|
bun run devcheck
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Modern MCP protocol support and bounded EPA search pagination."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.3.1 — 2026-08-22
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **MCP protocol** — adopts `@cyanheads/mcp-ts-core` ^0.12.3 and MCP SDK v2: modern MCP protocol support, undeclared tool input fields are rejected, and error results advertise a validation-compatible envelope.
|
|
12
|
+
- **Packaging** — updates the Bun build images and package manager pin to 1.4.0, and removes platform-specific native bindings from MCPB bundles.
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **`epa_search_tri_releases`** batches facility IDs to avoid oversized URLs and respects `limit`.
|
|
17
|
+
- **`epa_search_violations`, `epa_search_facilities`, and `epa_search_water_systems`** honor `limit` while preserving full-match totals where the upstream exposes them.
|
|
18
|
+
|
|
19
|
+
## Dependencies
|
|
20
|
+
|
|
21
|
+
- `@cyanheads/mcp-ts-core` ^0.10.14 → ^0.12.3
|
|
22
|
+
- `@biomejs/biome` ^2.5.2 → ^2.5.9
|
|
23
|
+
- `@types/node` ^25.9.3 → ^26.2.0
|
|
24
|
+
- `ignore` ^7.0.5 → ^7.0.6
|
|
25
|
+
- `tsc-alias` ^1.8.17 → ^1.9.2
|
|
26
|
+
- `typescript` ^6.0.3 → ^7.0.2
|
|
27
|
+
- `vitest` ^4.1.9 → ^4.1.11
|
|
@@ -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`
|
package/changelog/template.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
# to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
|
|
5
5
|
# with a concrete date.
|
|
6
6
|
|
|
7
|
-
# Required. One-line GitHub Release-style headline. 350 character cap
|
|
8
|
-
# Default short and scannable. Don't pad, don't stitch
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# Required. One-line GitHub Release-style headline. 350 character cap — a
|
|
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 one
|
|
10
|
+
# headline the release is about. Quotes required: unquoted YAML treats
|
|
11
|
+
# `: ` inside the value as a key separator and fails GitHub's strict parser.
|
|
11
12
|
summary: ""
|
|
12
13
|
|
|
13
14
|
# Set `true` when consumers must change code to upgrade: API removals,
|
|
@@ -15,16 +16,19 @@ summary: ""
|
|
|
15
16
|
# usage. Flagged as `Breaking` in the rollup.
|
|
16
17
|
breaking: false
|
|
17
18
|
|
|
18
|
-
# Set `true`
|
|
19
|
-
#
|
|
20
|
-
#
|
|
19
|
+
# Set `true` ONLY for a security fix in THIS project's own source code — a
|
|
20
|
+
# vulnerability or hardening in code you ship. A dependency or transitive CVE
|
|
21
|
+
# bump is routine maintenance, NOT a security release: record it under
|
|
22
|
+
# `## Dependencies` (with the advisory ID) and leave this `false`. When true,
|
|
23
|
+
# pairs with the `## Security` section below and flags `Security` in the rollup.
|
|
21
24
|
security: false
|
|
22
25
|
|
|
23
26
|
# Optional free-form notes for maintenance agents processing this release.
|
|
24
27
|
# Not rendered in CHANGELOG — consumed by agents running `maintenance` on
|
|
25
|
-
# downstream servers.
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
+
# downstream servers. ADOPTION STEPS ONLY — new files to create, fields to
|
|
29
|
+
# populate, one-time migration steps. Never a second rendering of the body:
|
|
30
|
+
# if a body bullet already says it, name the bullet's symbol instead of
|
|
31
|
+
# re-explaining. Omit the field entirely when there's nothing to say.
|
|
28
32
|
# agent-notes: |
|
|
29
33
|
# <instructions for downstream maintenance agents>
|
|
30
34
|
---
|
|
@@ -39,17 +43,54 @@ security: false
|
|
|
39
43
|
each bullet with the symbol or concept name in **bold** so they can skip
|
|
40
44
|
what's irrelevant and zoom in on what's not.
|
|
41
45
|
|
|
42
|
-
Tone: terse, fact-dense, not verbose.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
Tone: terse, fact-dense, not verbose. Bullet shape: **symbol** + what
|
|
47
|
+
changed + at most one consumer-facing caveat. One sentence by default, two
|
|
48
|
+
when the second carries weight — a bullet past ~40 words or three sentences
|
|
49
|
+
is wrong. The depth lives one hop away: the linked issue carries the why,
|
|
50
|
+
the commit diff carries the how. The changelog names what changed and what
|
|
51
|
+
a consumer does about it; a reader who wants mechanism opens the link.
|
|
52
|
+
|
|
53
|
+
Model length on THIS guide, never on the previous entry — entries modeled
|
|
54
|
+
on entries compound.
|
|
55
|
+
|
|
56
|
+
Cut (each has shipped as a wall of text; these are the cruft):
|
|
57
|
+
- History/justification narration — how the bug worked, why the old
|
|
58
|
+
behavior was wrong. One short clause at most; the issue carries the story.
|
|
59
|
+
- Design-rationale defense — "chosen over Y because…", "guarding the
|
|
60
|
+
getter is not enough…". That is the author arguing with a reviewer;
|
|
61
|
+
reviewers read the PR, not the changelog.
|
|
62
|
+
- Defensive unchanged-clauses — "X is unchanged", "byte-identical to
|
|
63
|
+
<prev>". Keep one only where its absence would cause a real misread,
|
|
64
|
+
as a short parenthetical.
|
|
65
|
+
- Edge-case inventories — marker lists, not-flagged lists, escape tables.
|
|
66
|
+
Tests and the issue carry those.
|
|
67
|
+
- Mechanism walkthroughs (JSDoc, CLAUDE.md/AGENTS.md, or the relevant
|
|
68
|
+
skill own those), ceremonial framings ("This release introduces…"),
|
|
69
|
+
backwards-compat paragraphs, file-by-file test enumerations. Prefer
|
|
70
|
+
code/symbol names over English re-explanations.
|
|
71
|
+
|
|
72
|
+
Verified ≠ included: the every-claim-verified-from-the-diff rule bounds
|
|
73
|
+
the TRUTH of what you write, never the AMOUNT.
|
|
74
|
+
|
|
75
|
+
Example — same fact, right size:
|
|
76
|
+
|
|
77
|
+
TOO LONG: **`fetchWithTimeout`'s `timeoutMs` bounds the whole exchange**
|
|
78
|
+
(#341). `fetch` resolves once headers arrive and the deadline was
|
|
79
|
+
cleared as the helper returned, so a peer that answered promptly and
|
|
80
|
+
then stalled the stream held the request open indefinitely. A 2xx
|
|
81
|
+
carrying a body now comes back as a passthrough wrapper that disarms
|
|
82
|
+
the deadline when the body closes, errors, or is cancelled; …
|
|
83
|
+
[+90 more words of mechanism and edge cases]
|
|
84
|
+
|
|
85
|
+
RIGHT: **`fetchWithTimeout`'s `timeoutMs` now bounds the whole
|
|
86
|
+
exchange, not just the headers** (#341). A stalled body aborts with
|
|
87
|
+
the same `Timeout` error; the returned `Response` is a wrapper, so
|
|
88
|
+
identity assertions (`toBe(response)`) no longer hold.
|
|
50
89
|
|
|
51
90
|
Narrative intro: skip by default. Add one short sentence only when the
|
|
52
|
-
release theme genuinely needs framing the bullets can't carry.
|
|
91
|
+
release theme genuinely needs framing the bullets can't carry. When many
|
|
92
|
+
bullets share one upgrade consequence, state it ONCE — intro line or
|
|
93
|
+
agent-notes — never per bullet.
|
|
53
94
|
|
|
54
95
|
Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
|
|
55
96
|
Fixed, Security. Include only sections with entries; delete the rest
|
|
@@ -76,30 +117,13 @@ security: false
|
|
|
76
117
|
in that unrelated item's metadata.
|
|
77
118
|
|
|
78
119
|
TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
|
|
79
|
-
via `gh release create --notes-from-tag`.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Dependency bumps: ← section header
|
|
87
|
-
← blank line
|
|
88
|
-
- `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
|
|
89
|
-
← blank line
|
|
90
|
-
Changed: ← only sections with entries
|
|
91
|
-
← blank line
|
|
92
|
-
- `format()` output includes `query` in text mode
|
|
93
|
-
← blank line
|
|
94
|
-
Added:
|
|
95
|
-
← blank line
|
|
96
|
-
- `manifest.json` scaffolded for MCPB bundle support
|
|
97
|
-
- Install badges (Claude Desktop, Cursor, VS Code)
|
|
98
|
-
← blank line
|
|
99
|
-
<N> tests pass; `bun run devcheck` clean. ← footer
|
|
100
|
-
|
|
101
|
-
Never a flat comma-separated string. Always structured markdown with
|
|
102
|
-
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.
|
|
103
127
|
-->
|
|
104
128
|
|
|
105
129
|
## Added
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
declare const ServerConfigSchema: z.ZodObject<{
|
|
7
|
-
airNowApiKey: z.
|
|
7
|
+
airNowApiKey: z.ZodOptional<z.ZodString>;
|
|
8
8
|
echoBaseUrl: z.ZodDefault<z.ZodString>;
|
|
9
9
|
dmapBaseUrl: z.ZodDefault<z.ZodString>;
|
|
10
10
|
airNowBaseUrl: z.ZodDefault<z.ZodString>;
|
|
11
|
-
ejscreenBaseUrl: z.
|
|
11
|
+
ejscreenBaseUrl: z.ZodDefault<z.ZodString>;
|
|
12
12
|
}, z.core.$strip>;
|
|
13
13
|
export type ServerConfig = z.infer<typeof ServerConfigSchema>;
|
|
14
14
|
export declare function getServerConfig(): ServerConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;;
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;;iBA4BtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAS9C"}
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
7
7
|
const ServerConfigSchema = z.object({
|
|
8
|
-
// Optional: an empty or whitespace-only value (e.g. an unfilled `${AIRNOW_API_KEY}`
|
|
9
|
-
// placeholder from a bundle/compose config) is treated as absent so the server still
|
|
10
|
-
// starts. When absent, index.ts skips registering epa_get_air_quality and AirNow init.
|
|
11
8
|
airNowApiKey: z
|
|
12
|
-
.
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.optional()
|
|
13
12
|
.describe('AirNow API key (free registration at docs.airnowapi.org). Optional — when unset, epa_get_air_quality is not registered and the other tools run without it.'),
|
|
14
13
|
echoBaseUrl: z
|
|
15
14
|
.string()
|
|
@@ -26,11 +25,10 @@ const ServerConfigSchema = z.object({
|
|
|
26
25
|
.url()
|
|
27
26
|
.default('https://www.airnowapi.org/aq')
|
|
28
27
|
.describe('AirNow API base URL'),
|
|
29
|
-
// Optional override. Bundle/compose configs may inject an unfilled
|
|
30
|
-
// `${EJSCREEN_API_BASE_URL}` placeholder as an empty string — treat empty or
|
|
31
|
-
// whitespace-only as absent so the default applies instead of failing `.url()`.
|
|
32
28
|
ejscreenBaseUrl: z
|
|
33
|
-
.
|
|
29
|
+
.string()
|
|
30
|
+
.url()
|
|
31
|
+
.default('https://api.ejanalysis.com')
|
|
34
32
|
.describe('EJScreen (EJAM) API base URL — the community-maintained EJScreen rehost'),
|
|
35
33
|
});
|
|
36
34
|
let _config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,YAAY,EAAE,CAAC;SACZ,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,4JAA4J,CAC7J;IACH,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,+BAA+B,CAAC;SACxC,QAAQ,CAAC,mBAAmB,CAAC;IAChC,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,kCAAkC,CAAC;SAC3C,QAAQ,CAAC,mBAAmB,CAAC;IAChC,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,8BAA8B,CAAC;SACvC,QAAQ,CAAC,qBAAqB,CAAC;IAClC,eAAe,EAAE,CAAC;SACf,MAAM,EAAE;SACR,GAAG,EAAE;SACL,OAAO,CAAC,4BAA4B,CAAC;SACrC,QAAQ,CAAC,yEAAyE,CAAC;CACvF,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,YAAY,EAAE,gBAAgB;QAC9B,WAAW,EAAE,mBAAmB;QAChC,WAAW,EAAE,mBAAmB;QAChC,aAAa,EAAE,qBAAqB;QACpC,eAAe,EAAE,uBAAuB;KACzC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|