@cyanheads/epa-mcp-server 0.3.2 → 0.4.0
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 +13 -10
- package/CLAUDE.md +13 -10
- package/Dockerfile +108 -37
- package/README.md +67 -44
- package/changelog/0.3.x/0.3.3.md +34 -0
- package/changelog/0.4.x/0.4.0.md +35 -0
- package/dist/mcp-server/tools/definitions/get-air-quality.tool.d.ts +23 -9
- 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 +166 -98
- 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.map +1 -1
- package/dist/mcp-server/tools/definitions/get-ejscreen.tool.js +1 -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 +5 -14
- package/dist/mcp-server/tools/definitions/get-facility.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts +2 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js +10 -14
- package/dist/mcp-server/tools/definitions/get-tri-releases.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/index.d.ts +51 -18
- package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-facilities.tool.js +9 -16
- 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 +4 -14
- package/dist/mcp-server/tools/definitions/search-superfund.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts +10 -3
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-tri-releases.tool.js +50 -10
- package/dist/mcp-server/tools/definitions/search-tri-releases.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 +3 -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 +23 -10
- package/dist/mcp-server/tools/definitions/search-water-systems.tool.js.map +1 -1
- package/dist/mcp-server/tools/format-lbs.d.ts +14 -0
- package/dist/mcp-server/tools/format-lbs.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-lbs.js +16 -0
- package/dist/mcp-server/tools/format-lbs.js.map +1 -0
- package/dist/mcp-server/tools/format-location.d.ts +18 -0
- package/dist/mcp-server/tools/format-location.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-location.js +26 -0
- package/dist/mcp-server/tools/format-location.js.map +1 -0
- package/dist/mcp-server/tools/format-tri-release.d.ts +16 -0
- package/dist/mcp-server/tools/format-tri-release.d.ts.map +1 -0
- package/dist/mcp-server/tools/format-tri-release.js +23 -0
- package/dist/mcp-server/tools/format-tri-release.js.map +1 -0
- package/dist/services/airnow/airnow-service.d.ts +10 -29
- package/dist/services/airnow/airnow-service.d.ts.map +1 -1
- package/dist/services/airnow/airnow-service.js +148 -95
- package/dist/services/airnow/airnow-service.js.map +1 -1
- package/dist/services/airnow/types.d.ts +62 -23
- package/dist/services/airnow/types.d.ts.map +1 -1
- package/dist/services/airnow/types.js +2 -1
- package/dist/services/airnow/types.js.map +1 -1
- package/dist/services/dmap/dmap-service.d.ts +41 -2
- package/dist/services/dmap/dmap-service.d.ts.map +1 -1
- package/dist/services/dmap/dmap-service.js +231 -116
- package/dist/services/dmap/dmap-service.js.map +1 -1
- package/dist/services/dmap/types.d.ts +17 -4
- package/dist/services/dmap/types.d.ts.map +1 -1
- package/dist/services/echo/echo-service.d.ts.map +1 -1
- package/dist/services/echo/echo-service.js +8 -5
- package/dist/services/echo/echo-service.js.map +1 -1
- package/dist/services/ejscreen/ejscreen-service.js +1 -1
- package/dist/services/ejscreen/ejscreen-service.js.map +1 -1
- package/package.json +11 -10
- package/server.json +16 -24
|
@@ -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.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "epa_get_air_quality moves to AirNow's 2026 web services (breaking: distance_miles removed, observation coordinates removed, aqi optional); TRI search runs as one DMAP join with a working county filter and opt-in release breakdown; water-system has_violation counts open violations; hosted endpoint at epa.caseyjhand.com."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: false
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Callers of epa_get_air_quality: drop `distance_miles`, stop reading `latitude`/`longitude`, treat `hourObserved` as a string and `aqi` as optional.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 0.4.0 — 2026-10-08
|
|
10
|
+
|
|
11
|
+
## Added
|
|
12
|
+
|
|
13
|
+
- **`epa_get_air_quality`** reads `observation/current/ziplatlong/` and `forecast/current/`, returns per-site readings (`siteName`, `siteID`, `reportingAgency`), the agency `discussion`, and an `attribution` credits block ([#30](https://github.com/cyanheads/epa-mcp-server/issues/30)).
|
|
14
|
+
- **`epa_search_tri_releases`** takes opt-in `include_release_breakdown` for air, water, land, and underground-injection releases at the cost of one more upstream request ([#17](https://github.com/cyanheads/epa-mcp-server/issues/17)).
|
|
15
|
+
- **`epa_search_tri_releases`** records carry `countyName`; TRI quantities reported in grams (dioxin and dioxin-like compounds) are converted to pounds and carry `reportedUnit`.
|
|
16
|
+
- **Hosted endpoint** `https://epa.caseyjhand.com/mcp` is listed in `server.json` remotes and the README.
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
|
|
20
|
+
- **`epa_get_air_quality` (breaking)** removes the `distance_miles` input and the `latitude`/`longitude` output fields, retypes `hourObserved`, and makes `aqi` optional (category-only forecasts omit it). `forecast_date` is optional, filters the issued forecast, and is rejected in current mode.
|
|
21
|
+
- **`epa_search_tri_releases`** is one DMAP join instead of an unbounded facility walk ([#32](https://github.com/cyanheads/epa-mcp-server/issues/32)); the `county` filter matches `county_name` after dropping a trailing County, Parish, or Borough ([#27](https://github.com/cyanheads/epa-mcp-server/issues/27)).
|
|
22
|
+
- **`epa_search_water_systems`** `has_violation` keeps systems with an open violation, in chunked lookups with a scan notice, and works with `zip_code`; `zip_code` is documented as the address of record.
|
|
23
|
+
- **`epa_get_facility`**, **`epa_search_facilities`**, **`epa_search_superfund`** render latitude and longitude independently ([#24](https://github.com/cyanheads/epa-mcp-server/issues/24)) and pound quantities at full precision.
|
|
24
|
+
- **`facility_id`** guidance on `epa_get_tri_releases` points at `epa_search_tri_releases` ([#21](https://github.com/cyanheads/epa-mcp-server/issues/21)).
|
|
25
|
+
- **Tests** assert exact tool sets, error messages, and per-field output across the suite.
|
|
26
|
+
|
|
27
|
+
## Fixed
|
|
28
|
+
|
|
29
|
+
- **DMAP row ranges** are 1-based and inclusive.
|
|
30
|
+
- **`epa_search_tri_releases`** under-limit searches no longer fail during enrichment.
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
- `@cyanheads/mcp-ts-core` ^0.13.13 → ^0.13.14 (framework skills synced).
|
|
35
|
+
- `pino-pretty` ^13.1.3 → ^13.2.0.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Tool for getting AQI observations or
|
|
2
|
+
* @fileoverview Tool for getting current AQI observations or the issued AQI forecast for a
|
|
3
|
+
* location from AirNow.
|
|
3
4
|
* @module mcp-server/tools/definitions/get-air-quality.tool
|
|
4
5
|
*/
|
|
5
6
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
@@ -13,34 +14,47 @@ export declare const getAirQualityTool: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
13
14
|
forecast: "forecast";
|
|
14
15
|
}>>;
|
|
15
16
|
forecast_date: z.ZodOptional<z.ZodString>;
|
|
16
|
-
distance_miles: z.ZodDefault<z.ZodNumber>;
|
|
17
17
|
}, z.core.$strip>, z.ZodObject<{
|
|
18
18
|
observations: z.ZodArray<z.ZodObject<{
|
|
19
19
|
reportingArea: z.ZodOptional<z.ZodString>;
|
|
20
|
+
reportingAreaCode: z.ZodOptional<z.ZodString>;
|
|
20
21
|
stateCode: z.ZodOptional<z.ZodString>;
|
|
21
|
-
latitude: z.ZodOptional<z.ZodNumber>;
|
|
22
|
-
longitude: z.ZodOptional<z.ZodNumber>;
|
|
23
22
|
dateObserved: z.ZodOptional<z.ZodString>;
|
|
24
|
-
hourObserved: z.ZodOptional<z.
|
|
23
|
+
hourObserved: z.ZodOptional<z.ZodString>;
|
|
25
24
|
localTimeZone: z.ZodOptional<z.ZodString>;
|
|
25
|
+
discussion: z.ZodOptional<z.ZodString>;
|
|
26
26
|
readings: z.ZodArray<z.ZodObject<{
|
|
27
27
|
parameterName: z.ZodString;
|
|
28
|
-
aqi: z.ZodNumber
|
|
28
|
+
aqi: z.ZodOptional<z.ZodNumber>;
|
|
29
29
|
categoryNumber: z.ZodOptional<z.ZodNumber>;
|
|
30
30
|
categoryName: z.ZodOptional<z.ZodString>;
|
|
31
|
+
siteName: z.ZodOptional<z.ZodString>;
|
|
32
|
+
siteID: z.ZodOptional<z.ZodString>;
|
|
33
|
+
reportingAgency: z.ZodOptional<z.ZodString>;
|
|
34
|
+
dateIssue: z.ZodOptional<z.ZodString>;
|
|
35
|
+
dateValid: z.ZodOptional<z.ZodString>;
|
|
36
|
+
forecastAgency: z.ZodOptional<z.ZodString>;
|
|
37
|
+
actionDay: z.ZodOptional<z.ZodBoolean>;
|
|
31
38
|
}, z.core.$strip>>;
|
|
32
39
|
}, z.core.$strip>>;
|
|
33
40
|
mode: z.ZodString;
|
|
34
41
|
message: z.ZodOptional<z.ZodString>;
|
|
42
|
+
attribution: z.ZodString;
|
|
35
43
|
}, z.core.$strip>, readonly [{
|
|
36
44
|
readonly reason: "no_location";
|
|
37
45
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
38
46
|
readonly when: "Neither zip_code nor latitude+longitude was provided.";
|
|
39
47
|
readonly recovery: "Provide either zip_code or both latitude and longitude to identify the location.";
|
|
40
48
|
}, {
|
|
41
|
-
readonly reason: "
|
|
49
|
+
readonly reason: "forecast_date_needs_forecast_mode";
|
|
42
50
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
43
|
-
readonly when: "
|
|
44
|
-
readonly recovery: "
|
|
51
|
+
readonly when: "forecast_date was set while mode is \"current\", where it has no effect.";
|
|
52
|
+
readonly recovery: "Set mode to \"forecast\" to filter by forecast_date, or omit forecast_date.";
|
|
53
|
+
}, {
|
|
54
|
+
readonly reason: "invalid_zip_code";
|
|
55
|
+
readonly code: JsonRpcErrorCode.ValidationError;
|
|
56
|
+
readonly when: "AirNow rejected zip_code as invalid (HTTP 400).";
|
|
57
|
+
readonly recovery: "Check that zip_code is a valid 5-digit US ZIP code, or pass latitude and longitude instead.";
|
|
58
|
+
readonly thrownBy: "service";
|
|
45
59
|
}], undefined>;
|
|
46
60
|
//# sourceMappingURL=get-air-quality.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-air-quality.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-air-quality.tool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"get-air-quality.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-air-quality.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA+BjE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0R5B,CAAC"}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Tool for getting AQI observations or
|
|
2
|
+
* @fileoverview Tool for getting current AQI observations or the issued AQI forecast for a
|
|
3
|
+
* location from AirNow.
|
|
3
4
|
* @module mcp-server/tools/definitions/get-air-quality.tool
|
|
4
5
|
*/
|
|
5
6
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
7
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
8
|
import { getAirNowService } from '../../../services/airnow/airnow-service.js';
|
|
9
|
+
const AIRNOW_PROGRAM = 'the U.S. EPA AirNow program (www.airnow.gov)';
|
|
10
|
+
const PRELIMINARY_NOTICE = 'AirNow data are preliminary and unverified; do not use them for regulatory, trend, or enforcement decisions.';
|
|
11
|
+
/** Credit line naming the agencies behind the returned readings and the AirNow program. */
|
|
12
|
+
function attributionFor(mode, areas) {
|
|
13
|
+
const agencies = [
|
|
14
|
+
...new Set(areas.flatMap((area) => area.readings.flatMap((r) => {
|
|
15
|
+
const agency = r.reportingAgency ?? r.forecastAgency;
|
|
16
|
+
return agency ? [agency] : [];
|
|
17
|
+
}))),
|
|
18
|
+
];
|
|
19
|
+
const list = (items) => new Intl.ListFormat('en', { type: 'conjunction' }).format(items);
|
|
20
|
+
const credit = mode === 'current'
|
|
21
|
+
? `Data courtesy of ${list([...agencies, AIRNOW_PROGRAM])}.`
|
|
22
|
+
: agencies.length > 0
|
|
23
|
+
? `Forecasts issued by ${list(agencies)} and distributed by ${AIRNOW_PROGRAM}.`
|
|
24
|
+
: `Forecasts distributed by ${AIRNOW_PROGRAM}.`;
|
|
25
|
+
return `${credit} ${PRELIMINARY_NOTICE}`;
|
|
26
|
+
}
|
|
8
27
|
export const getAirQualityTool = tool('epa_get_air_quality', {
|
|
9
28
|
title: 'Get Air Quality Index',
|
|
10
|
-
description:
|
|
29
|
+
description: "Get current AQI observations or the issued AQI forecast for a US location from the AirNow API. Provide either zip_code or both latitude and longitude. Current mode returns the latest hourly NowCast AQI for each pollutant (PM2.5, ozone, PM10) from the closest monitor within 50 miles, with the monitor site name, site ID, and reporting agency; pollutants in one reporting area can come from different monitors. Forecast mode returns every day the reporting area's agency has issued (typically one to five), each with the AQI category, an action-day flag, and the issue date, plus the agency's forecast discussion when one exists; some agencies forecast a category without an AQI number. In forecast mode, set forecast_date to keep only the forecast valid on that date; current mode rejects it. Data are preliminary — suitable for awareness, not regulatory, trend, or enforcement decisions. Responses are cached for ~1 hour.",
|
|
11
30
|
annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: true },
|
|
12
31
|
input: z.object({
|
|
13
|
-
zip_code: z.string().optional().describe('5-digit ZIP code for the location'),
|
|
32
|
+
zip_code: z.string().optional().describe('5-digit US ZIP code for the location'),
|
|
14
33
|
latitude: z
|
|
15
34
|
.number()
|
|
16
35
|
.optional()
|
|
@@ -22,18 +41,11 @@ export const getAirQualityTool = tool('epa_get_air_quality', {
|
|
|
22
41
|
mode: z
|
|
23
42
|
.enum(['current', 'forecast'])
|
|
24
43
|
.default('current')
|
|
25
|
-
.describe('Data mode: "current" for latest
|
|
44
|
+
.describe('Data mode: "current" for the latest hourly observations, "forecast" for every forecast day the reporting area has issued'),
|
|
26
45
|
forecast_date: z
|
|
27
46
|
.string()
|
|
28
47
|
.optional()
|
|
29
|
-
.describe('
|
|
30
|
-
distance_miles: z
|
|
31
|
-
.number()
|
|
32
|
-
.int()
|
|
33
|
-
.min(0)
|
|
34
|
-
.max(300)
|
|
35
|
-
.default(25)
|
|
36
|
-
.describe('Search radius in miles for finding reporting stations. Default 25 miles.'),
|
|
48
|
+
.describe('Forecast mode only (rejected in current mode): keep just the forecast valid on this date (YYYY-MM-DD). Omit to get every issued forecast day. When no issued day matches, the result is empty and its message lists the issued dates.'),
|
|
37
49
|
}),
|
|
38
50
|
output: z.object({
|
|
39
51
|
observations: z
|
|
@@ -42,48 +54,91 @@ export const getAirQualityTool = tool('epa_get_air_quality', {
|
|
|
42
54
|
reportingArea: z
|
|
43
55
|
.string()
|
|
44
56
|
.optional()
|
|
45
|
-
.describe('Name of the AQI reporting area (e.g. "Seattle-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
.describe('Name of the AQI reporting area (e.g. "Seattle-Bellevue-Kent Valley")'),
|
|
58
|
+
reportingAreaCode: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('AirNow reporting area code (e.g. "wa004"). Forecast mode only.'),
|
|
62
|
+
stateCode: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('2-letter state code of the reporting area. Forecast mode only.'),
|
|
49
66
|
dateObserved: z
|
|
50
67
|
.string()
|
|
51
68
|
.optional()
|
|
52
|
-
.describe('Observation
|
|
69
|
+
.describe('Observation date (YYYY-MM-DD). Current mode only.'),
|
|
53
70
|
hourObserved: z
|
|
54
|
-
.
|
|
71
|
+
.string()
|
|
55
72
|
.optional()
|
|
56
|
-
.describe('
|
|
73
|
+
.describe('Local hour of the observation as "HH:00". Current mode only.'),
|
|
57
74
|
localTimeZone: z
|
|
58
75
|
.string()
|
|
59
76
|
.optional()
|
|
60
|
-
.describe('Local time zone abbreviation (e.g. "
|
|
77
|
+
.describe('Local time zone abbreviation for hourObserved (e.g. "PDT"). Current mode only.'),
|
|
78
|
+
discussion: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("The forecasting agency's discussion for this area, verbatim (may contain HTML). Forecast mode only; omitted when the agency issued none."),
|
|
61
82
|
readings: z
|
|
62
83
|
.array(z
|
|
63
84
|
.object({
|
|
64
85
|
parameterName: z
|
|
65
86
|
.string()
|
|
66
|
-
.describe('Pollutant name (e.g. "PM2.5", "
|
|
67
|
-
aqi: z
|
|
87
|
+
.describe('Pollutant name as AirNow reports it (e.g. "PM2.5", "OZONE", "PM10")'),
|
|
88
|
+
aqi: z
|
|
89
|
+
.number()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Air Quality Index value. Omitted when the agency issued only a category (category-only forecast).'),
|
|
68
92
|
categoryNumber: z
|
|
69
93
|
.number()
|
|
70
94
|
.optional()
|
|
71
|
-
.describe('AQI category number: 1=Good, 2=Moderate, 3=Unhealthy for Sensitive Groups, 4=Unhealthy, 5=Very Unhealthy, 6=Hazardous'),
|
|
95
|
+
.describe('AQI category number: 1=Good, 2=Moderate, 3=Unhealthy for Sensitive Groups, 4=Unhealthy, 5=Very Unhealthy, 6=Hazardous. Omitted when AirNow reports an unrecognized category.'),
|
|
72
96
|
categoryName: z
|
|
73
97
|
.string()
|
|
74
98
|
.optional()
|
|
75
99
|
.describe('AQI category name (e.g. "Good", "Moderate", "Unhealthy")'),
|
|
100
|
+
siteName: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe('Monitor site that produced this observation. Current mode only.'),
|
|
104
|
+
siteID: z
|
|
105
|
+
.string()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe('AirNow ID of the monitor site. Current mode only.'),
|
|
108
|
+
reportingAgency: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe('Agency that reported this observation. Current mode only.'),
|
|
112
|
+
dateIssue: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('Date the forecast was issued (YYYY-MM-DD). Forecast mode only.'),
|
|
116
|
+
dateValid: z
|
|
117
|
+
.string()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe('Date the forecast applies to (YYYY-MM-DD). Forecast mode only.'),
|
|
120
|
+
forecastAgency: z
|
|
121
|
+
.string()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('Agency that issued the forecast. Forecast mode only.'),
|
|
124
|
+
actionDay: z
|
|
125
|
+
.boolean()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe('True when the agency declared an air quality action day for this date. Forecast mode only.'),
|
|
76
128
|
})
|
|
77
|
-
.describe('AQI reading for
|
|
78
|
-
.describe('Per-pollutant AQI readings for this reporting area
|
|
129
|
+
.describe('AQI reading for one pollutant (and, in forecast mode, one valid date)'))
|
|
130
|
+
.describe('Per-pollutant AQI readings for this reporting area'),
|
|
79
131
|
})
|
|
80
|
-
.describe('AQI
|
|
132
|
+
.describe('AQI readings for one reporting area — per observed hour in current mode, per area across all forecast days in forecast mode'))
|
|
81
133
|
.describe('AQI observation or forecast records grouped by reporting area'),
|
|
82
134
|
mode: z.string().describe('Data mode used: "current" or "forecast"'),
|
|
83
135
|
message: z
|
|
84
136
|
.string()
|
|
85
137
|
.optional()
|
|
86
|
-
.describe(
|
|
138
|
+
.describe("Why the result is empty: AirNow's own explanation, the issued forecast dates when forecast_date matched none, or a coverage hint. Absent when data is returned."),
|
|
139
|
+
attribution: z
|
|
140
|
+
.string()
|
|
141
|
+
.describe('Credit to the reporting or forecasting agencies and the U.S. EPA AirNow program, with the preliminary-data notice. Reproduce it when passing this data on.'),
|
|
87
142
|
}),
|
|
88
143
|
errors: [
|
|
89
144
|
{
|
|
@@ -93,102 +148,115 @@ export const getAirQualityTool = tool('epa_get_air_quality', {
|
|
|
93
148
|
recovery: 'Provide either zip_code or both latitude and longitude to identify the location.',
|
|
94
149
|
},
|
|
95
150
|
{
|
|
96
|
-
reason: '
|
|
151
|
+
reason: 'forecast_date_needs_forecast_mode',
|
|
152
|
+
code: JsonRpcErrorCode.ValidationError,
|
|
153
|
+
when: 'forecast_date was set while mode is "current", where it has no effect.',
|
|
154
|
+
recovery: 'Set mode to "forecast" to filter by forecast_date, or omit forecast_date.',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
reason: 'invalid_zip_code',
|
|
97
158
|
code: JsonRpcErrorCode.ValidationError,
|
|
98
|
-
when: '
|
|
99
|
-
recovery: '
|
|
159
|
+
when: 'AirNow rejected zip_code as invalid (HTTP 400).',
|
|
160
|
+
recovery: 'Check that zip_code is a valid 5-digit US ZIP code, or pass latitude and longitude instead.',
|
|
161
|
+
thrownBy: 'service',
|
|
100
162
|
},
|
|
101
163
|
],
|
|
102
164
|
async handler(input, ctx) {
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const zip = input.zip_code;
|
|
107
|
-
const loc = zip !== undefined && zip.trim() !== ''
|
|
165
|
+
// Zip takes precedence over lat/lng; a blank zip (form clients) falls through to lat/lng.
|
|
166
|
+
const zip = input.zip_code?.trim();
|
|
167
|
+
const location = zip
|
|
108
168
|
? { kind: 'zip', zipCode: zip }
|
|
109
169
|
: input.latitude !== undefined && input.longitude !== undefined
|
|
110
170
|
? { kind: 'latlng', latitude: input.latitude, longitude: input.longitude }
|
|
111
171
|
: undefined;
|
|
112
|
-
if (!
|
|
113
|
-
throw ctx.fail('no_location', 'Provide either zip_code or both latitude and longitude.'
|
|
114
|
-
...ctx.recoveryFor('no_location'),
|
|
115
|
-
});
|
|
172
|
+
if (!location) {
|
|
173
|
+
throw ctx.fail('no_location', 'Provide either zip_code or both latitude and longitude.');
|
|
116
174
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (input.forecast_date === undefined || input.forecast_date.trim() === '') {
|
|
121
|
-
throw ctx.fail('forecast_date_required', 'forecast_date (YYYY-MM-DD) is required when mode is "forecast".', {
|
|
122
|
-
...ctx.recoveryFor('forecast_date_required'),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
forecastDate = input.forecast_date;
|
|
175
|
+
const forecastDate = input.forecast_date?.trim() || undefined;
|
|
176
|
+
if (forecastDate && input.mode === 'current') {
|
|
177
|
+
throw ctx.fail('forecast_date_needs_forecast_mode', `forecast_date "${forecastDate}" only filters forecasts; current mode returns the latest observations.`);
|
|
126
178
|
}
|
|
127
179
|
ctx.log.info('epa_get_air_quality', {
|
|
128
180
|
mode: input.mode,
|
|
129
|
-
|
|
130
|
-
|
|
181
|
+
location: location.kind,
|
|
182
|
+
forecastDate,
|
|
131
183
|
});
|
|
132
184
|
const service = getAirNowService();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
185
|
+
const lookup = input.mode === 'forecast'
|
|
186
|
+
? await service.getForecast(location, ctx)
|
|
187
|
+
: await service.getCurrent(location, ctx);
|
|
188
|
+
const observations = input.mode === 'forecast' && forecastDate
|
|
189
|
+
? lookup.areas.flatMap((area) => {
|
|
190
|
+
const readings = area.readings.filter((r) => r.dateValid === forecastDate);
|
|
191
|
+
return readings.length > 0 ? [{ ...area, readings }] : [];
|
|
192
|
+
})
|
|
193
|
+
: lookup.areas;
|
|
194
|
+
ctx.log.info('epa_get_air_quality completed', { areas: observations.length });
|
|
195
|
+
const attribution = attributionFor(input.mode, observations);
|
|
196
|
+
if (observations.length > 0)
|
|
197
|
+
return { observations, mode: input.mode, attribution };
|
|
198
|
+
const where = location.kind === 'zip'
|
|
199
|
+
? `zip_code="${location.zipCode}"`
|
|
200
|
+
: `latitude=${location.latitude}, longitude=${location.longitude}`;
|
|
201
|
+
let message;
|
|
202
|
+
if (lookup.noDataMessage !== undefined) {
|
|
203
|
+
message = `AirNow returned no data for ${where}: "${lookup.noDataMessage}"`;
|
|
143
204
|
}
|
|
144
|
-
else {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
latitude: loc.latitude,
|
|
150
|
-
longitude: loc.longitude,
|
|
151
|
-
date: forecastDate,
|
|
152
|
-
distanceMiles: input.distance_miles,
|
|
153
|
-
}, ctx);
|
|
205
|
+
else if (lookup.areas.length > 0) {
|
|
206
|
+
const issued = [
|
|
207
|
+
...new Set(lookup.areas.flatMap((a) => a.readings.flatMap((r) => r.dateValid ?? []))),
|
|
208
|
+
].sort();
|
|
209
|
+
message = `No forecast valid on ${forecastDate} for ${where}. ${issued.length > 0 ? `Issued forecast dates: ${issued.join(', ')}.` : 'AirNow listed no valid dates.'} Omit forecast_date to get every issued day.`;
|
|
154
210
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const location = loc.kind === 'zip'
|
|
158
|
-
? `zip_code="${input.zip_code}"`
|
|
159
|
-
: `lat=${input.latitude}, lng=${input.longitude}`;
|
|
160
|
-
return {
|
|
161
|
-
observations: [],
|
|
162
|
-
mode: input.mode,
|
|
163
|
-
message: `No AQI data found for ${location} within ${input.distance_miles} miles. Try increasing distance_miles or check that the location is within the US.`,
|
|
164
|
-
};
|
|
211
|
+
else {
|
|
212
|
+
message = `No AQI data found for ${where}. AirNow observations come from monitors within 50 miles and forecasts from the location's reporting area; check that the location is in the US, or try a nearby ZIP code.`;
|
|
165
213
|
}
|
|
166
|
-
return { observations, mode: input.mode };
|
|
214
|
+
return { observations: [], mode: input.mode, message, attribution };
|
|
167
215
|
},
|
|
168
216
|
format: (result) => {
|
|
169
|
-
const lines = [
|
|
170
|
-
|
|
217
|
+
const lines = [
|
|
218
|
+
`## Air Quality Index — ${result.mode === 'forecast' ? 'Forecast' : 'Current Observations'} (mode: ${result.mode})`,
|
|
219
|
+
];
|
|
171
220
|
if (result.message)
|
|
172
221
|
lines.push(`\n> ${result.message}`);
|
|
173
|
-
for (const
|
|
174
|
-
lines.push(`\n### ${
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
222
|
+
for (const area of result.observations) {
|
|
223
|
+
lines.push(`\n### ${area.reportingArea ?? 'Unknown Area'}`);
|
|
224
|
+
const meta = [
|
|
225
|
+
area.stateCode && `**State:** ${area.stateCode}`,
|
|
226
|
+
area.reportingAreaCode && `**Reporting area code:** ${area.reportingAreaCode}`,
|
|
227
|
+
].filter(Boolean);
|
|
228
|
+
if (meta.length > 0)
|
|
229
|
+
lines.push(meta.join(' | '));
|
|
230
|
+
if (area.dateObserved || area.hourObserved) {
|
|
231
|
+
const time = area.hourObserved
|
|
232
|
+
? ` at ${area.hourObserved}${area.localTimeZone ? ` ${area.localTimeZone}` : ''}`
|
|
180
233
|
: '';
|
|
181
|
-
lines.push(`**
|
|
234
|
+
lines.push(`**Observed:** ${area.dateObserved ?? 'date not reported'}${time}`);
|
|
182
235
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
236
|
+
for (const r of area.readings) {
|
|
237
|
+
const label = r.dateValid ? `${r.dateValid} ${r.parameterName}` : r.parameterName;
|
|
238
|
+
const value = r.aqi !== undefined
|
|
239
|
+
? `AQI ${r.aqi}${r.categoryName ? ` (${r.categoryName})` : ''}`
|
|
240
|
+
: (r.categoryName ?? 'Category not reported');
|
|
241
|
+
const category = r.categoryNumber !== undefined ? ` [category ${r.categoryNumber}]` : '';
|
|
242
|
+
const categoryOnly = r.aqi === undefined ? ' (category only, no AQI number)' : '';
|
|
243
|
+
const site = r.siteName || r.siteID
|
|
244
|
+
? ` — ${[r.siteName, r.siteID && `site ${r.siteID}`].filter(Boolean).join(', ')}`
|
|
245
|
+
: '';
|
|
246
|
+
const details = [
|
|
247
|
+
r.reportingAgency,
|
|
248
|
+
(r.dateIssue || r.forecastAgency) &&
|
|
249
|
+
['issued', r.dateIssue, r.forecastAgency && `by ${r.forecastAgency}`]
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.join(' '),
|
|
252
|
+
r.actionDay !== undefined && `action day: ${r.actionDay ? 'yes' : 'no'}`,
|
|
253
|
+
].filter(Boolean);
|
|
254
|
+
lines.push(`- **${label}:** ${value}${category}${categoryOnly}${site}${details.length > 0 ? ` · ${details.join(' · ')}` : ''}`);
|
|
190
255
|
}
|
|
256
|
+
if (area.discussion)
|
|
257
|
+
lines.push(`\n**Discussion:**\n${area.discussion}`);
|
|
191
258
|
}
|
|
259
|
+
lines.push(`\n**Source:** ${result.attribution}`);
|
|
192
260
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
193
261
|
},
|
|
194
262
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-air-quality.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-air-quality.tool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"get-air-quality.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-air-quality.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-ejscreen.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ejscreen.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"get-ejscreen.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-ejscreen.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQjE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+S1B,CAAC"}
|
|
@@ -179,7 +179,7 @@ export const getEjscreenTool = tool('epa_get_ejscreen', {
|
|
|
179
179
|
async handler(input, ctx) {
|
|
180
180
|
const bufferMiles = input.unit === 'kilometers' ? input.distance * MILES_PER_KM : input.distance;
|
|
181
181
|
if (bufferMiles > MAX_BUFFER_MILES) {
|
|
182
|
-
throw ctx.fail('buffer_too_large', `Buffer of ${bufferMiles.toFixed(2)} miles exceeds the EJAM API limit of ${MAX_BUFFER_MILES} miles
|
|
182
|
+
throw ctx.fail('buffer_too_large', `Buffer of ${bufferMiles.toFixed(2)} miles exceeds the EJAM API limit of ${MAX_BUFFER_MILES} miles.`);
|
|
183
183
|
}
|
|
184
184
|
ctx.log.info('epa_get_ejscreen', {
|
|
185
185
|
lat: input.latitude,
|