@cliwant/mcp-sam-gov 1.3.0 → 1.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.
Files changed (83) hide show
  1. package/README.ja.md +15 -7
  2. package/README.ko.md +15 -7
  3. package/README.md +48 -8
  4. package/dist/bea.d.ts +1 -1
  5. package/dist/bea.js +1 -1
  6. package/dist/census-economic.d.ts +1 -1
  7. package/dist/census-economic.d.ts.map +1 -1
  8. package/dist/census-economic.js +12 -6
  9. package/dist/census-economic.js.map +1 -1
  10. package/dist/cms-facility.d.ts +112 -0
  11. package/dist/cms-facility.d.ts.map +1 -0
  12. package/dist/cms-facility.js +311 -0
  13. package/dist/cms-facility.js.map +1 -0
  14. package/dist/cms-hospital.d.ts +105 -0
  15. package/dist/cms-hospital.d.ts.map +1 -0
  16. package/dist/cms-hospital.js +290 -0
  17. package/dist/cms-hospital.js.map +1 -0
  18. package/dist/cms-supplier.d.ts +133 -0
  19. package/dist/cms-supplier.d.ts.map +1 -0
  20. package/dist/cms-supplier.js +414 -0
  21. package/dist/cms-supplier.js.map +1 -0
  22. package/dist/cms-utilization.d.ts +113 -0
  23. package/dist/cms-utilization.d.ts.map +1 -0
  24. package/dist/cms-utilization.js +328 -0
  25. package/dist/cms-utilization.js.map +1 -0
  26. package/dist/courtlistener.d.ts +115 -0
  27. package/dist/courtlistener.d.ts.map +1 -0
  28. package/dist/courtlistener.js +398 -0
  29. package/dist/courtlistener.js.map +1 -0
  30. package/dist/cpsc.d.ts +81 -0
  31. package/dist/cpsc.d.ts.map +1 -0
  32. package/dist/cpsc.js +283 -0
  33. package/dist/cpsc.js.map +1 -0
  34. package/dist/dol.d.ts +2 -2
  35. package/dist/dol.js +5 -5
  36. package/dist/dol.js.map +1 -1
  37. package/dist/epa-envirofacts.d.ts +97 -0
  38. package/dist/epa-envirofacts.d.ts.map +1 -0
  39. package/dist/epa-envirofacts.js +292 -0
  40. package/dist/epa-envirofacts.js.map +1 -0
  41. package/dist/fred.d.ts +1 -1
  42. package/dist/fred.js +1 -1
  43. package/dist/keys.d.ts +6 -5
  44. package/dist/keys.d.ts.map +1 -1
  45. package/dist/keys.js +25 -6
  46. package/dist/keys.js.map +1 -1
  47. package/dist/nhtsa.d.ts +91 -0
  48. package/dist/nhtsa.d.ts.map +1 -0
  49. package/dist/nhtsa.js +263 -0
  50. package/dist/nhtsa.js.map +1 -0
  51. package/dist/nonprofit.d.ts +116 -0
  52. package/dist/nonprofit.d.ts.map +1 -0
  53. package/dist/nonprofit.js +342 -0
  54. package/dist/nonprofit.js.map +1 -0
  55. package/dist/openfda-device.d.ts +85 -0
  56. package/dist/openfda-device.d.ts.map +1 -0
  57. package/dist/openfda-device.js +277 -0
  58. package/dist/openfda-device.js.map +1 -0
  59. package/dist/openfda.d.ts +133 -0
  60. package/dist/openfda.d.ts.map +1 -0
  61. package/dist/openfda.js +402 -0
  62. package/dist/openfda.js.map +1 -0
  63. package/dist/server.d.ts.map +1 -1
  64. package/dist/server.js +666 -7
  65. package/dist/server.js.map +1 -1
  66. package/package.json +2 -1
  67. package/src/bea.ts +1 -1
  68. package/src/census-economic.ts +12 -6
  69. package/src/cms-facility.ts +379 -0
  70. package/src/cms-hospital.ts +344 -0
  71. package/src/cms-supplier.ts +527 -0
  72. package/src/cms-utilization.ts +389 -0
  73. package/src/courtlistener.ts +465 -0
  74. package/src/cpsc.ts +333 -0
  75. package/src/dol.ts +5 -5
  76. package/src/epa-envirofacts.ts +342 -0
  77. package/src/fred.ts +1 -1
  78. package/src/keys.ts +27 -6
  79. package/src/nhtsa.ts +352 -0
  80. package/src/nonprofit.ts +460 -0
  81. package/src/openfda-device.ts +356 -0
  82. package/src/openfda.ts +495 -0
  83. package/src/server.ts +765 -7
@@ -0,0 +1,292 @@
1
+ /**
2
+ * epa-envirofacts.ts — EPA Envirofacts RESTful data service (`data.epa.gov`,
3
+ * `/efservice`), the `tri_facility` table (ADR-0059). KEYLESS.
4
+ *
5
+ * WHAT IT ADDS: `epa_tri_facilities` — a location/compliance lane: look up EPA
6
+ * Toxics Release Inventory (TRI) reporting facilities by state / facility-name /
7
+ * county. This is the demand-side environmental-footprint complement to the
8
+ * market-sizing (Census CBP) and macro (FRED) sources — a place-of-performance
9
+ * environmental screen for a given geography.
10
+ *
11
+ * ★THE TWO-REQUEST PATTERN (the load-bearing P1 honesty): the efservice REST API
12
+ * embeds filters as PATH SEGMENTS and reports NO total in the data slice. So the
13
+ * EXACT total comes from a SEPARATE count sub-query — the identical filter path
14
+ * with a `/count/JSON` tail returns `[{"TOTALQUERYRESULTS": 1247}]`. This tool
15
+ * runs the count FIRST (best-effort) then the data slice: totalAvailable =
16
+ * TOTALQUERYRESULTS (P1, EXACT), NEVER the returned rows' length. If the count
17
+ * sub-query fails or is absent, totalAvailable falls to null + a disclosing note
18
+ * (never a length-faked total) and the data slice still returns.
19
+ *
20
+ * The module writes ZERO fetch/coercion/error/meta code — it REUSES `getJson`
21
+ * (redirect:"error") / `driftError` (datasource.ts), `str`/`num` (coerce.ts,
22
+ * null-never-empty-string / null-never-0), and `withMeta`·`buildMeta` (meta.ts,
23
+ * offset pagination + totalAvailable). It MIRRORS census-economic.ts's
24
+ * fixed-host SSRF idiom (a single host const + a post-construction hostname/
25
+ * protocol assertion + redirect:"error") and datagov-catalog.ts's schema_drift
26
+ * catch-ladder (ToolErrorCarrier rethrow FIRST so a 5xx keeps its taxonomy →
27
+ * SyntaxError→driftError → bare rethrow).
28
+ *
29
+ * GET https://data.epa.gov/efservice/tri_facility/state_abbr/{ST}
30
+ * [/facility_name/CONTAINING/{NAME}] [/county_name/CONTAINING/{COUNTY}]
31
+ * /count/JSON → [{ "TOTALQUERYRESULTS": <int> }] (the total)
32
+ * GET …same filter path… /rows/{offset}:{offset+limit-1}/JSON
33
+ * → [{ tri_facility_id, facility_name, street_address, city_name,
34
+ * county_name, state_abbr, zip_code, region, fac_closed_ind, … }, …]
35
+ *
36
+ * ★ SSRF (the load-bearing guard — values ride as PATH SEGMENTS, not query params):
37
+ * the host is a compile-time literal (`EPA_HOST`); the table + column names + the
38
+ * `CONTAINING` operator + `rows`/`count`/`JSON` are all MODULE literals. Every
39
+ * USER value is BOTH charclass-validated (state `^[A-Za-z]{2}$`; facilityName/
40
+ * county letters/digits/space/&/-/. only, rejecting `/` and `..` path-traversal)
41
+ * AND `encodeURIComponent`-encoded before it joins the path. `offset`/`limit` are
42
+ * coerced to non-negative integers. A post-construction hostname/protocol
43
+ * assertion + `redirect:"error"` fail closed on any off-host 3xx.
44
+ *
45
+ * ★ HONESTY (ADR-0059 P1–P5, live-verified 2026-07-15 on data.epa.gov):
46
+ * [input] require at least `state` OR `facilityName` — an all-empty query is
47
+ * REFUSED with invalid_input (0 fetch) so the whole national table is
48
+ * never scanned.
49
+ * [P1] totalAvailable = the count sub-query's TOTALQUERYRESULTS (EXACT — e.g.
50
+ * VA = 1247), NOT the slice length. hasMore = offset+returned < total.
51
+ * Count fails/absent ⇒ totalAvailable:null + a disclosing note.
52
+ * [P2] an empty array ⇒ honest empty (returned:0, complete:true). getJson maps
53
+ * a 4xx/5xx via errorFromResponse and THROWS (503 ⇒ upstream_unavailable,
54
+ * 400 ⇒ invalid_input, 404 ⇒ not_found); a 200 non-array/non-JSON body ⇒
55
+ * schema_drift (NEVER a fabricated empty).
56
+ * [P3] fac_closed_ind ("0"/"1" live; "N"/"Y" per the schema) ⇒ a normalized
57
+ * boolean `closed` — an UNRECOGNIZED value ⇒ null (never a fabricated
58
+ * false). Addresses/names are strings via `str` (null-never-empty-string).
59
+ * [P4] a data body that is not an array ⇒ driftError; a count body missing
60
+ * TOTALQUERYRESULTS ⇒ totalAvailable:null (handled, not a crash).
61
+ */
62
+ import { ToolErrorCarrier } from "./errors.js";
63
+ import { getJson, driftError } from "./datasource.js";
64
+ import { str, num } from "./coerce.js";
65
+ import { withMeta } from "./meta.js";
66
+ // Re-export the shared honesty coercions (single audited copy in ./coerce.js) so a
67
+ // regression fails together across sources. NO local num/str.
68
+ export { num, str };
69
+ // ─── SSRF core: the single fixed host + module-literal path pieces ──
70
+ const EPA_HOST = "data.epa.gov";
71
+ const EPA_TABLE = "tri_facility";
72
+ // HOST+path label — surfaces in ToolError.upstreamEndpoint; keyless, so no token
73
+ // can ever appear here.
74
+ const EPA_LABEL = "epa-envirofacts:/efservice/tri_facility";
75
+ // ─── Validation charclasses (SSRF + "verify the input" honesty) ───
76
+ // state rides as a PATH SEGMENT → strictly 2 ASCII letters (the efservice filter is
77
+ // case-insensitive; live-verified `va` === `VA`).
78
+ const STATE_RE = /^[A-Za-z]{2}$/;
79
+ // facilityName / county ride as PATH SEGMENTS after `.../CONTAINING/`. Allow only
80
+ // letters / digits / space / & / - / . (a superset of real facility names); this
81
+ // REJECTS `/` (path injection) and any other separator. `..` path-traversal is
82
+ // rejected explicitly below (the charclass permits a lone `.`, so `..` must be
83
+ // caught separately).
84
+ const NAME_RE = /^[A-Za-z0-9 &.\-]+$/;
85
+ const NAME_MAX = 100;
86
+ const LIMIT_MIN = 1;
87
+ const LIMIT_MAX = 100;
88
+ const LIMIT_DEFAULT = 25;
89
+ // ─── Honesty notes (ADR-0059 required set) ────────────────────────
90
+ const CLOSED_NOTE = "`closed` is the normalized EPA fac_closed_ind ('0'/'N' → false = active, '1'/'Y' → true = closed); an unrecognized value is null (unknown), NEVER a fabricated false. A closed facility no longer actively reports to TRI.";
91
+ const NOMINAL_NOTE = "These are EPA TRI (Toxics Release Inventory) REPORTING facilities for the geography — a nominal environmental-footprint screen, NOT a compliance, enforcement, or violation determination. Use ECHO / enforcement sources for compliance status.";
92
+ const COUNT_FALLBACK_NOTE = "The count sub-query (…/count/JSON) failed or did not report TOTALQUERYRESULTS, so totalAvailable is null (unknown) — it was NOT faked from the returned row count. hasMore is a heuristic (a full page ⇒ likely more); re-page with offset to confirm.";
93
+ /**
94
+ * Normalize EPA's fac_closed_ind to a boolean. Live values are "0" (active) / "1"
95
+ * (closed); the schema also documents "N"/"Y". Anything else ⇒ null (unknown) —
96
+ * NEVER a fabricated false (P3).
97
+ */
98
+ export function normalizeClosed(v) {
99
+ const s = str(v);
100
+ if (s === null)
101
+ return null;
102
+ const t = s.toUpperCase();
103
+ if (t === "1" || t === "Y" || t === "YES" || t === "TRUE")
104
+ return true;
105
+ if (t === "0" || t === "N" || t === "NO" || t === "FALSE")
106
+ return false;
107
+ return null;
108
+ }
109
+ /** Map ONE efservice row → the curated facility shape (every scalar via `str`). */
110
+ function mapFacility(row) {
111
+ const r = (row ?? {});
112
+ return {
113
+ triFacilityId: str(r.tri_facility_id),
114
+ facilityName: str(r.facility_name),
115
+ streetAddress: str(r.street_address),
116
+ city: str(r.city_name),
117
+ county: str(r.county_name),
118
+ state: str(r.state_abbr),
119
+ zip: str(r.zip_code),
120
+ region: str(r.region),
121
+ closed: normalizeClosed(r.fac_closed_ind),
122
+ };
123
+ }
124
+ // ─── SSRF-guarded fetch (fixed host + hostname assertion + redirect:"error") ──
125
+ /**
126
+ * GET one efservice JSON resource at a MODULE-BUILT path (all user values already
127
+ * charclass-validated + encodeURIComponent-encoded by the caller). Builds
128
+ * `https://${EPA_HOST}${path}` on the FIXED host, asserts the CONSTRUCTED URL's
129
+ * hostname === the host over https (belt-and-suspenders), and sets
130
+ * `redirect:"error"` (an off-host 3xx must NOT be followed). Keyless — no headers.
131
+ */
132
+ async function getEpa(path) {
133
+ const url = `https://${EPA_HOST}${path}`;
134
+ const built = new URL(url);
135
+ if (built.hostname !== EPA_HOST || built.protocol !== "https:") {
136
+ throw new ToolErrorCarrier({
137
+ kind: "invalid_input",
138
+ message: `Constructed EPA Envirofacts URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the fixed host ${JSON.stringify(EPA_HOST)} over https — refusing to fetch (SSRF safety).`,
139
+ retryable: false,
140
+ upstreamEndpoint: EPA_LABEL,
141
+ });
142
+ }
143
+ return getJson(built.toString(), { label: EPA_LABEL, redirect: "error" });
144
+ }
145
+ /** Validate + encode ONE user path-segment value (facilityName / county). */
146
+ function validateName(value, field) {
147
+ if (value.length > NAME_MAX || !NAME_RE.test(value) || value.includes("..")) {
148
+ throw new ToolErrorCarrier({
149
+ kind: "invalid_input",
150
+ retryable: false,
151
+ message: `Invalid ${field} ${JSON.stringify(value)} — allowed: letters, digits, space, & - . (≤${NAME_MAX} chars); '/' and '..' are rejected (it rides in the request PATH as a segment; it is strictly validated to prevent path injection).`,
152
+ upstreamEndpoint: EPA_LABEL,
153
+ });
154
+ }
155
+ return encodeURIComponent(value);
156
+ }
157
+ /**
158
+ * Look up EPA TRI reporting facilities by state / facilityName / county →
159
+ * normalized facility rows + honest `_meta`. Requires at least `state` OR
160
+ * `facilityName` (an all-empty query is refused). Runs a count sub-query FIRST for
161
+ * the EXACT total (P1), then the data slice; a count failure degrades to
162
+ * totalAvailable:null + a note (never a length-faked total).
163
+ */
164
+ export async function triFacilities(args) {
165
+ // ── [input guard] require at least state OR facilityName (never scan the whole
166
+ // national table). ──
167
+ const hasState = args.state !== undefined && args.state !== "";
168
+ const hasName = args.facilityName !== undefined && args.facilityName !== "";
169
+ if (!hasState && !hasName) {
170
+ throw new ToolErrorCarrier({
171
+ kind: "invalid_input",
172
+ retryable: false,
173
+ message: "epa_tri_facilities requires at least `state` (2-letter, e.g. 'VA') OR `facilityName` — an all-empty query would scan the entire national TRI table and is refused. Add a filter and retry.",
174
+ upstreamEndpoint: EPA_LABEL,
175
+ });
176
+ }
177
+ // ── Validate + build the filter path segments (SSRF: charclass + encode each). ──
178
+ const segments = ["efservice", EPA_TABLE];
179
+ const filtersApplied = [];
180
+ if (hasState) {
181
+ const state = args.state;
182
+ if (!STATE_RE.test(state)) {
183
+ throw new ToolErrorCarrier({
184
+ kind: "invalid_input",
185
+ retryable: false,
186
+ message: `Invalid state ${JSON.stringify(state)} — expected a 2-letter state code (^[A-Za-z]{2}$), e.g. "VA" (it rides in the request PATH; it is strictly validated).`,
187
+ upstreamEndpoint: EPA_LABEL,
188
+ });
189
+ }
190
+ segments.push("state_abbr", encodeURIComponent(state));
191
+ filtersApplied.push(`state:${state}`);
192
+ }
193
+ if (hasName) {
194
+ const enc = validateName(args.facilityName, "facilityName");
195
+ segments.push("facility_name", "CONTAINING", enc);
196
+ filtersApplied.push(`facilityName~${args.facilityName}`);
197
+ }
198
+ if (args.county !== undefined && args.county !== "") {
199
+ const enc = validateName(args.county, "county");
200
+ segments.push("county_name", "CONTAINING", enc);
201
+ filtersApplied.push(`county~${args.county}`);
202
+ }
203
+ // ── Coerce limit / offset to non-negative integers (belt-and-suspenders behind
204
+ // the server Zod; a DIRECT handler call bypasses Zod). ──
205
+ let limit = LIMIT_DEFAULT;
206
+ if (typeof args.limit === "number" && Number.isFinite(args.limit)) {
207
+ limit = Math.trunc(args.limit);
208
+ if (limit < LIMIT_MIN)
209
+ limit = LIMIT_MIN;
210
+ if (limit > LIMIT_MAX)
211
+ limit = LIMIT_MAX;
212
+ }
213
+ let offset = 0;
214
+ if (typeof args.offset === "number" && Number.isFinite(args.offset) && args.offset > 0) {
215
+ offset = Math.trunc(args.offset);
216
+ }
217
+ const filterPath = "/" + segments.join("/");
218
+ // ── (1) The COUNT sub-query FIRST (best-effort — the EXACT total, P1). Any
219
+ // failure (network/5xx/drift/missing field) degrades to totalAvailable:null +
220
+ // a disclosing note; it NEVER throws and NEVER fakes the total from the slice
221
+ // length. ──
222
+ let totalAvailable = null;
223
+ let countFailed = false;
224
+ try {
225
+ const countBody = await getEpa(`${filterPath}/count/JSON`);
226
+ if (Array.isArray(countBody) &&
227
+ countBody.length > 0 &&
228
+ countBody[0] !== null &&
229
+ typeof countBody[0] === "object") {
230
+ const t = num(countBody[0].TOTALQUERYRESULTS);
231
+ if (t !== null && t >= 0) {
232
+ totalAvailable = t;
233
+ }
234
+ else {
235
+ countFailed = true; // present body but no usable TOTALQUERYRESULTS (P4)
236
+ }
237
+ }
238
+ else {
239
+ countFailed = true; // count body not the expected [{...}] shape
240
+ }
241
+ }
242
+ catch {
243
+ countFailed = true; // any count error ⇒ degrade, never propagate (P1)
244
+ }
245
+ // ── (2) The DATA slice — the authoritative request. efservice rows are INCLUSIVE
246
+ // on both ends: rows/{offset}:{offset+limit-1}. Its errors follow P2. ──
247
+ const end = offset + limit - 1;
248
+ const dataPath = `${filterPath}/rows/${offset}:${end}/JSON`;
249
+ // Catch-ladder (datagov-catalog shape, VERBATIM): preserve the 4xx/5xx/timeout
250
+ // ToolErrorCarrier taxonomy FIRST; reclassify a 200 non-JSON SyntaxError to
251
+ // schema_drift SECOND; bare-rethrow LAST.
252
+ let body;
253
+ try {
254
+ body = await getEpa(dataPath);
255
+ }
256
+ catch (e) {
257
+ if (e instanceof ToolErrorCarrier)
258
+ throw e;
259
+ if (e instanceof SyntaxError)
260
+ throw driftError(EPA_LABEL, "EPA Envirofacts returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
261
+ throw e;
262
+ }
263
+ // [P4] the data body MUST be an array (a non-array 200 is drift, never a
264
+ // fabricated empty).
265
+ if (!Array.isArray(body)) {
266
+ throw driftError(EPA_LABEL, "EPA Envirofacts shape drift — the /rows/…/JSON response must be a JSON array of facilities.");
267
+ }
268
+ const facilities = body.map(mapFacility);
269
+ const returned = facilities.length;
270
+ // ── [P1] pagination. total known ⇒ hasMore = offset+returned < total; total
271
+ // unknown (count degraded) ⇒ a full page is the honest heuristic for "more". ──
272
+ const hasMore = totalAvailable !== null
273
+ ? offset + returned < totalAvailable
274
+ : returned === limit;
275
+ const nextOffset = hasMore ? offset + returned : null;
276
+ const notes = [CLOSED_NOTE, NOMINAL_NOTE];
277
+ if (countFailed)
278
+ notes.push(COUNT_FALLBACK_NOTE);
279
+ const meta = {
280
+ source: `${EPA_HOST} EPA Envirofacts /efservice/${EPA_TABLE} (TRI facilities; keyless)`,
281
+ keylessMode: true,
282
+ returned,
283
+ totalAvailable,
284
+ filtersApplied,
285
+ filtersDropped: [],
286
+ fieldsUnavailable: [],
287
+ pagination: { offset, limit, hasMore, nextOffset },
288
+ notes,
289
+ };
290
+ return withMeta({ facilities }, meta);
291
+ }
292
+ //# sourceMappingURL=epa-envirofacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epa-envirofacts.js","sourceRoot":"","sources":["../src/epa-envirofacts.ts"],"names":[],"mappings":"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"}
package/dist/fred.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  * FRED has NO keyless tier: every request needs `&api_key=`, and a missing/bad
9
9
  * key returns HTTP 400 `{error_code, error_message}`. So, honestly: with NO
10
10
  * `FRED_API_KEY` these two tools THROW an `invalid_input` config error BEFORE any
11
- * fetch (never a fake-empty, never a keyless-pretend). The other 112 tools stay
11
+ * fetch (never a fake-empty, never a keyless-pretend). The other tools stay
12
12
  * keyless — this key is scoped to this one source. (Contrast the OPTIONAL keys of
13
13
  * datagov/bls/nvd, which lift a tier but are not required.)
14
14
  *
package/dist/fred.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * FRED has NO keyless tier: every request needs `&api_key=`, and a missing/bad
9
9
  * key returns HTTP 400 `{error_code, error_message}`. So, honestly: with NO
10
10
  * `FRED_API_KEY` these two tools THROW an `invalid_input` config error BEFORE any
11
- * fetch (never a fake-empty, never a keyless-pretend). The other 112 tools stay
11
+ * fetch (never a fake-empty, never a keyless-pretend). The other tools stay
12
12
  * keyless — this key is scoped to this one source. (Contrast the OPTIONAL keys of
13
13
  * datagov/bls/nvd, which lift a tier but are not required.)
14
14
  *
package/dist/keys.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Why this exists
5
5
  * ----------------
6
- * The server rides 31 federal sources. MOST are fully keyless. But the set of
6
+ * The server rides dozens of federal data sources. MOST are fully keyless. But the set of
7
7
  * *optional* keys (raise a rate limit, unlock one filter) plus the four *required*
8
8
  * keys (Census business-patterns, FRED, BEA Regional, DOL data) has grown to the point where a user — or
9
9
  * the AI driving the server — cannot tell, without reading source code:
@@ -20,8 +20,9 @@
20
20
  * `process.env.<NAME>` — DATA_GOV_API_KEY (datagovKey.ts), SAM_GOV_API_KEY
21
21
  * (server.ts), BLS_API_KEY (bls.ts), NVD_API_KEY (nvd.ts), SOCRATA_APP_TOKEN
22
22
  * (socrata.ts), CENSUS_API_KEY (census-economic.ts), FRED_API_KEY (fred.ts),
23
- * BEA_API_KEY (bea.ts), DOL_API_KEY (dol.ts), LDA_API_KEY (lda.ts). No invented
24
- * keys, sources, or signup URLs.
23
+ * BEA_API_KEY (bea.ts), DOL_API_KEY (dol.ts), LDA_API_KEY (lda.ts),
24
+ * OPENFDA_API_KEY (openfda.ts), COURTLISTENER_API_TOKEN (courtlistener.ts).
25
+ * No invented keys, sources, or signup URLs.
25
26
  */
26
27
  /** One registry entry describing a single API key the server can use. */
27
28
  export type KeyRegistryEntry = {
@@ -39,11 +40,11 @@ export type KeyRegistryEntry = {
39
40
  note: string;
40
41
  };
41
42
  /**
42
- * The 10 keys the server reads — code-grounded, no inventions.
43
+ * The 12 keys the server reads — code-grounded, no inventions.
43
44
  *
44
45
  * REQUIRED (4): CENSUS_API_KEY, FRED_API_KEY, BEA_API_KEY, DOL_API_KEY — those sources
45
46
  * have no keyless tier, so the tool throws without them (DOL_API_KEY gates ONLY
46
- * dol_get_dataset; the DOL catalog, dol_list_datasets, is keyless). OPTIONAL (6):
47
+ * dol_get_dataset; the DOL catalog, dol_list_datasets, is keyless). OPTIONAL (8):
47
48
  * everything else works keyless; a key only raises a rate limit or unlocks a single filter.
48
49
  */
49
50
  export declare const KEY_REGISTRY: readonly KeyRegistryEntry[];
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EA8F1C,CAAC;AAQX,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAErE,yCAAyC;AACzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,sFAAsF;IACtF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,kDAAkD;IAClD,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,kBAAkB,CAYjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CA4C/C"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,gBAAgB,EAkH1C,CAAC;AAQX,sFAAsF;AACtF,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG;IAAE,YAAY,EAAE,OAAO,CAAA;CAAE,CAAC;AAErE,yCAAyC;AACzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,sFAAsF;IACtF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,kDAAkD;IAClD,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,kBAAkB,CAYjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CA4C/C"}
package/dist/keys.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Why this exists
5
5
  * ----------------
6
- * The server rides 31 federal sources. MOST are fully keyless. But the set of
6
+ * The server rides dozens of federal data sources. MOST are fully keyless. But the set of
7
7
  * *optional* keys (raise a rate limit, unlock one filter) plus the four *required*
8
8
  * keys (Census business-patterns, FRED, BEA Regional, DOL data) has grown to the point where a user — or
9
9
  * the AI driving the server — cannot tell, without reading source code:
@@ -20,17 +20,18 @@
20
20
  * `process.env.<NAME>` — DATA_GOV_API_KEY (datagovKey.ts), SAM_GOV_API_KEY
21
21
  * (server.ts), BLS_API_KEY (bls.ts), NVD_API_KEY (nvd.ts), SOCRATA_APP_TOKEN
22
22
  * (socrata.ts), CENSUS_API_KEY (census-economic.ts), FRED_API_KEY (fred.ts),
23
- * BEA_API_KEY (bea.ts), DOL_API_KEY (dol.ts), LDA_API_KEY (lda.ts). No invented
24
- * keys, sources, or signup URLs.
23
+ * BEA_API_KEY (bea.ts), DOL_API_KEY (dol.ts), LDA_API_KEY (lda.ts),
24
+ * OPENFDA_API_KEY (openfda.ts), COURTLISTENER_API_TOKEN (courtlistener.ts).
25
+ * No invented keys, sources, or signup URLs.
25
26
  */
26
27
  import { readFileSync } from "node:fs";
27
28
  import { join } from "node:path";
28
29
  /**
29
- * The 10 keys the server reads — code-grounded, no inventions.
30
+ * The 12 keys the server reads — code-grounded, no inventions.
30
31
  *
31
32
  * REQUIRED (4): CENSUS_API_KEY, FRED_API_KEY, BEA_API_KEY, DOL_API_KEY — those sources
32
33
  * have no keyless tier, so the tool throws without them (DOL_API_KEY gates ONLY
33
- * dol_get_dataset; the DOL catalog, dol_list_datasets, is keyless). OPTIONAL (6):
34
+ * dol_get_dataset; the DOL catalog, dol_list_datasets, is keyless). OPTIONAL (8):
34
35
  * everything else works keyless; a key only raises a rate limit or unlocks a single filter.
35
36
  */
36
37
  export const KEY_REGISTRY = [
@@ -106,7 +107,7 @@ export const KEY_REGISTRY = [
106
107
  "US DOL enforcement data (dol_get_dataset; dol_list_datasets is keyless)",
107
108
  ],
108
109
  required: true,
109
- signupUrl: "https://dol.gov/developer",
110
+ signupUrl: "https://dataportal.dol.gov/registration",
110
111
  unlocks: "the dol_get_dataset tool (dataset records need a free key; dol_list_datasets works keyless)",
111
112
  note: "The DOL data endpoint needs a free key; the dataset CATALOG (dol_list_datasets) and agency list are keyless.",
112
113
  },
@@ -118,6 +119,24 @@ export const KEY_REGISTRY = [
118
119
  unlocks: "higher LDA API rate limits (anonymous access already works without it)",
119
120
  note: "Keyless by default (anonymous 200); a free token only raises the rate limit.",
120
121
  },
122
+ {
123
+ envVar: "OPENFDA_API_KEY",
124
+ sources: ["openFDA enforcement (openfda_enforcement)"],
125
+ required: false,
126
+ signupUrl: "https://open.fda.gov/apis/authentication/",
127
+ unlocks: "higher openFDA rate limits (keyless works without it — ~1000/day)",
128
+ note: "Keyless by default; a free key raises the rate limit.",
129
+ },
130
+ {
131
+ envVar: "COURTLISTENER_API_TOKEN",
132
+ sources: [
133
+ "CourtListener federal court opinions (courtlistener_search_opinions)",
134
+ ],
135
+ required: false,
136
+ signupUrl: "https://www.courtlistener.com/help/api/rest/",
137
+ unlocks: "higher CourtListener rate limits (anonymous search works without it)",
138
+ note: "Keyless by default (anonymous 200); a free token only raises the rate limit. Data = US federal court public records via CourtListener/Free Law Project.",
139
+ },
121
140
  ];
122
141
  /** true iff the env var is set to a non-empty (after-trim) string. */
123
142
  function isSet(envVar) {
package/dist/keys.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * nhtsa.ts — NHTSA VEHICLE SAFETY (api.nhtsa.gov) — the vehicle / parts /
3
+ * fleet supplier PRODUCT-SAFETY vetting lane (ADR-0057). Two keyless tools:
4
+ * • nhtsa_recalls — /recalls/recallsByVehicle?make=&model=&modelYear=
5
+ * • nhtsa_complaints — /complaints/complaintsByVehicle?make=&model=&modelYear=
6
+ * The cross-agency product-safety family alongside openFDA (medical) / CPSC
7
+ * (consumer goods): a manufacturer/component/safety-signal history for B2G
8
+ * supplier vetting.
9
+ *
10
+ * ★ KEYLESS — there is NO API key at all (no parameter, no header). This module
11
+ * touches NO key seam (no KEY_REGISTRY / keys.ts / API_KEYS.md). It REUSES the
12
+ * shared `getJson` (redirect:"error") / `driftError` fetch envelope, the `num`/
13
+ * `str` coercions (null-never-empty-string; a genuine 0 stays 0), and
14
+ * `withMeta`/`buildMeta` — and mirrors datagov-catalog.ts's fixed-host SSRF
15
+ * idiom + schema_drift catch-ladder verbatim.
16
+ *
17
+ * ★ PII — the complaints upstream response carries a `vin` field (an individual
18
+ * vehicle identifier). It is DELIBERATELY OMITTED from the curated output
19
+ * entirely — never surfaced, logged, or stored. The B2G value is the
20
+ * manufacturer / component / safety signal, NOT the VIN.
21
+ *
22
+ * ★ THE HONESTY PILLARS (P1-P4, live-verified 2026-07-15):
23
+ * P1: totalAvailable = `Count` (recalls) / `count` (complaints) — the REAL total.
24
+ * NHTSA returns the COMPLETE filtered set (no pagination), so in the normal
25
+ * case Count === results.length ⇒ complete:true. totalAvailable is NEVER
26
+ * fabricated: a PRESENT numeric Count is trusted verbatim; a MISSING Count
27
+ * falls back to results.length WITH an honest note (never invented).
28
+ * P2: results:[] (Count 0) ⇒ an HONEST EMPTY (returned:0, complete:true) — a bad
29
+ * make/model that returns 200+Count 0 is an honest no-match, NOT an error. A
30
+ * 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROW (never a fake empty); a 200
31
+ * non-JSON body ⇒ schema_drift.
32
+ * P3: booleans (crash/fire/parkIt/parkOutSide/overTheAirUpdate) preserved AS
33
+ * booleans (a non-boolean ⇒ null, never a fabricated false); counts
34
+ * (numberOfInjuries/numberOfDeaths) via `num` (a genuine 0 stays 0, NEVER
35
+ * null-for-0); dates as strings via `str`; Count/count via `num`.
36
+ * P4: `results` non-array ⇒ driftError; a Count/count that is PRESENT but a
37
+ * non-number ⇒ driftError (a broken total contract, never a fabricated empty).
38
+ * SSRF: fixed host `api.nhtsa.gov` (compile-time literal) + post-construction
39
+ * hostname/protocol assertion + redirect:"error"; make/model ride
40
+ * URLSearchParams (module-built, no raw passthrough); modelYear is
41
+ * ^\d{4}$; make/model are charclass-validated (letters/digits/space/hyphen,
42
+ * so a `../` or `%` can never reach the fixed path).
43
+ */
44
+ import { type MetaBundle } from "./meta.js";
45
+ export declare const NHTSA_HOST = "api.nhtsa.gov";
46
+ export declare const NHTSA_MODEL_YEAR_RE: RegExp;
47
+ export declare const NHTSA_MAKE_MODEL_RE: RegExp;
48
+ export type NhtsaVehicleArgs = {
49
+ make: string;
50
+ model: string;
51
+ modelYear: string;
52
+ };
53
+ export type NhtsaRecall = {
54
+ campaignNumber: string | null;
55
+ manufacturer: string | null;
56
+ component: string | null;
57
+ summary: string | null;
58
+ consequence: string | null;
59
+ remedy: string | null;
60
+ reportReceivedDate: string | null;
61
+ parkIt: boolean | null;
62
+ parkOutside: boolean | null;
63
+ overTheAirUpdate: boolean | null;
64
+ };
65
+ export type NhtsaComplaint = {
66
+ odiNumber: string | null;
67
+ manufacturer: string | null;
68
+ component: string | null;
69
+ summary: string | null;
70
+ crash: boolean | null;
71
+ fire: boolean | null;
72
+ numberOfInjuries: number | null;
73
+ numberOfDeaths: number | null;
74
+ dateOfIncident: string | null;
75
+ dateComplaintFiled: string | null;
76
+ };
77
+ /**
78
+ * Fetch NHTSA safety RECALLS for a make/model/modelYear → curated recall rows +
79
+ * honest `_meta`. KEYLESS. totalAvailable = the upstream `Count` (the REAL total —
80
+ * NHTSA returns the complete set, no pagination). A no-match (Count 0) ⇒ an honest
81
+ * empty; a 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROW; a 200 non-JSON ⇒ drift.
82
+ */
83
+ export declare function recalls(args: NhtsaVehicleArgs): Promise<MetaBundle>;
84
+ /**
85
+ * Fetch NHTSA consumer COMPLAINTS for a make/model/modelYear → curated complaint
86
+ * rows (★NO vin — PII omitted) + honest `_meta`. KEYLESS. totalAvailable = the
87
+ * upstream `count` (the REAL total). A no-match ⇒ honest empty; a 4xx ⇒
88
+ * invalid_input; a 5xx/timeout ⇒ THROW; a 200 non-JSON ⇒ drift.
89
+ */
90
+ export declare function complaints(args: NhtsaVehicleArgs): Promise<MetaBundle>;
91
+ //# sourceMappingURL=nhtsa.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nhtsa.d.ts","sourceRoot":"","sources":["../src/nhtsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAqB,MAAM,WAAW,CAAC;AAGzE,eAAO,MAAM,UAAU,kBAAkB,CAAC;AAY1C,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAC7C,eAAO,MAAM,mBAAmB,QAAqB,CAAC;AActD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAiHF,MAAM,MAAM,WAAW,GAAG;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC,CAAC;AA4CF;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmCzE;AAGD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAuC5E"}