@cliwant/mcp-sam-gov 1.1.0 → 1.3.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/README.ja.md +13 -7
- package/README.ko.md +13 -7
- package/README.md +38 -10
- package/dist/bea.d.ts +105 -0
- package/dist/bea.d.ts.map +1 -0
- package/dist/bea.js +303 -0
- package/dist/bea.js.map +1 -0
- package/dist/dol.d.ts +118 -0
- package/dist/dol.d.ts.map +1 -0
- package/dist/dol.js +421 -0
- package/dist/dol.js.map +1 -0
- package/dist/gsa-perdiem.d.ts +74 -0
- package/dist/gsa-perdiem.d.ts.map +1 -0
- package/dist/gsa-perdiem.js +296 -0
- package/dist/gsa-perdiem.js.map +1 -0
- package/dist/keys.d.ts +10 -8
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +37 -9
- package/dist/keys.js.map +1 -1
- package/dist/lda.d.ts +105 -0
- package/dist/lda.d.ts.map +1 -0
- package/dist/lda.js +317 -0
- package/dist/lda.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +252 -3
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/bea.ts +372 -0
- package/src/dol.ts +515 -0
- package/src/gsa-perdiem.ts +361 -0
- package/src/keys.ts +40 -9
- package/src/lda.ts +385 -0
- package/src/server.ts +288 -3
package/dist/lda.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lda.ts — US Senate LDA (Lobbying Disclosure Act) filings — the LOBBYING / B2G
|
|
3
|
+
* influence lane (ADR-0052). Who is paid HOW MUCH to lobby WHICH federal agency
|
|
4
|
+
* on WHICH issue — the registrant→client→government-entity signal that no
|
|
5
|
+
* contract/spending/grant source carries.
|
|
6
|
+
*
|
|
7
|
+
* ★ THIS IS A KEYLESS SOURCE WITH AN OPTIONAL KEY (the socrata.ts app-token
|
|
8
|
+
* lineage, NOT the census/fred/bea key-required lineage). The LDA REST API
|
|
9
|
+
* (lda.senate.gov/api/v1) serves anonymous GETs at HTTP 200 — it works with NO
|
|
10
|
+
* key. A free `LDA_API_KEY` only RAISES the shared rate limit; when set it rides
|
|
11
|
+
* ONLY as the `Authorization: Token <value>` request header (never the
|
|
12
|
+
* URL/label/_meta/notes/log — the K-test). When unset, NO auth header is sent
|
|
13
|
+
* (genuine keyless). This mirrors socrata's optional `X-App-Token` discipline.
|
|
14
|
+
*
|
|
15
|
+
* The module writes ZERO fetch/coercion/error/meta code of its own: it REUSES
|
|
16
|
+
* `getJson` (the shared fetch envelope, redirect:"error") / `driftError` /
|
|
17
|
+
* `num`·`str` (coerce.ts, null-never-0/empty) / `withMeta`·`buildMeta`.
|
|
18
|
+
*
|
|
19
|
+
* GET https://lda.senate.gov/api/v1/filings/
|
|
20
|
+
* ?filing_year=&filing_type=®istrant_name=&client_name=&lobbyist_name=
|
|
21
|
+
* &filing_specific_lobbying_issues=&government_entity=&page=&page_size=
|
|
22
|
+
* → { count, next, previous, results:[{ filing_uuid, filing_type,
|
|
23
|
+
* filing_type_display, filing_year, filing_period, filing_period_display,
|
|
24
|
+
* filing_document_url, income, expenses, dt_posted, termination_date,
|
|
25
|
+
* registrant:{name,…}|string, client:{name,…}|string,
|
|
26
|
+
* lobbying_activities:[{ general_issue_code, general_issue_code_display,
|
|
27
|
+
* description, government_entities:[{ name }] }], … }] }
|
|
28
|
+
*
|
|
29
|
+
* ★ HONESTY (ADR-0052 P1–P5):
|
|
30
|
+
* [P1] `count` is the API's REAL total (~1.95M) ⇒ totalAvailable = num(count),
|
|
31
|
+
* NEVER results.length. Page-based pagination (page/page_size, 1-based):
|
|
32
|
+
* returned = results.length, hasMore = page*pageSize < count, and the next
|
|
33
|
+
* page number is surfaced in a note. Reverting totalAvailable to
|
|
34
|
+
* results.length must go RED.
|
|
35
|
+
* [P2] getJson → fetchWithRetry taxonomy: a 400 (bad filter/param) ⇒
|
|
36
|
+
* invalid_input SURFACING the API's error body (re-read once on the error
|
|
37
|
+
* path); a 5xx/timeout ⇒ upstream_unavailable THROW; a 429 ⇒ rate_limited
|
|
38
|
+
* THROW honoring Retry-After (NEVER routed around); a genuine no-match
|
|
39
|
+
* (results:[]) ⇒ honest empty (returned:0); a 200 non-JSON ⇒ schema_drift.
|
|
40
|
+
* [P3] `income`/`expenses` are null-or-decimal-string ⇒ num() (null ⇒ null — NOT
|
|
41
|
+
* reported ≠ 0; a genuine "0" stays 0). A missing `lobbying_activities` /
|
|
42
|
+
* `government_entities` ⇒ empty arrays (never fabricated).
|
|
43
|
+
* [P4] `results` non-array or `count` non-number ⇒ driftError (never a
|
|
44
|
+
* fabricated empty/total).
|
|
45
|
+
* [SSRF] fixed host `lda.senate.gov`; a post-construction hostname/protocol
|
|
46
|
+
* assert + `redirect:"error"`; every filter VALUE rides URLSearchParams;
|
|
47
|
+
* `filingYear` / `page` / `pageSize` are charclass/range-guarded pre-fetch.
|
|
48
|
+
*/
|
|
49
|
+
import { ToolErrorCarrier } from "./errors.js";
|
|
50
|
+
import { getJson, driftError } from "./datasource.js";
|
|
51
|
+
import { num, str } from "./coerce.js";
|
|
52
|
+
import { withMeta } from "./meta.js";
|
|
53
|
+
// Re-export the shared honesty coercion (single audited copy in ./coerce.js —
|
|
54
|
+
// ADR-0005 v2 FIX-C) so a `num` regression fails together across sources.
|
|
55
|
+
export { num };
|
|
56
|
+
// ─── SSRF core: the single fixed host + base path ─────────────────
|
|
57
|
+
export const LDA_HOST = "lda.senate.gov";
|
|
58
|
+
const LDA_FILINGS_PATH = "/api/v1/filings/";
|
|
59
|
+
// HOST+path label — surfaces in ToolError.upstreamEndpoint; the optional key rides
|
|
60
|
+
// ONLY in the Authorization header, so no token can ever appear here.
|
|
61
|
+
const LDA_FILINGS_LABEL = "lda:/api/v1/filings";
|
|
62
|
+
// ─── Validation (SSRF + "verify the input" honesty) ───────────────
|
|
63
|
+
const YEAR_RE = /^\d{4}$/; // a single 4-digit filing year
|
|
64
|
+
const DEFAULT_PAGE = 1;
|
|
65
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
66
|
+
const MAX_PAGE_SIZE = 25; // the LDA API caps page_size at 25
|
|
67
|
+
// ─── Honesty notes (ADR-0052 required set) ────────────────────────
|
|
68
|
+
const KEYLESS_NOTE = "Keyless by default (anonymous LDA API access returns HTTP 200). An optional free LDA_API_KEY only RAISES the rate limit; when set it is sent ONLY as the `Authorization: Token …` request header and is NEVER logged, echoed, or placed in this response.";
|
|
69
|
+
const AMOUNT_NOTE = "incomeUsd / expensesUsd are parsed from the API's null-or-decimal-string income / expenses. A null (not reported) maps to null — NEVER 0; a genuine reported 0 is preserved as 0. A filing reports EITHER income (lobbying firms) OR expenses (in-house filers), so the other is typically null.";
|
|
70
|
+
const COUNT_TOTAL_NOTE = "totalAvailable is the LDA API's real total match count for the query (the whole corpus is ~1.95M filings) — NOT the number of rows on this page. Pagination is page-based (page / pageSize, 1-based); pass the next page number for more.";
|
|
71
|
+
// ─── The optional-key seam (value NEVER leaked past the Authorization header) ──
|
|
72
|
+
/**
|
|
73
|
+
* The optional Authorization header (keyless-first, socrata app-token lineage).
|
|
74
|
+
* Present ONLY when LDA_API_KEY is set (non-blank); the value is NEVER logged /
|
|
75
|
+
* never placed in the URL, label, `_meta`, or a note. When unset, `{}` (no header).
|
|
76
|
+
*/
|
|
77
|
+
export function ldaAuthHeader() {
|
|
78
|
+
const raw = process.env.LDA_API_KEY;
|
|
79
|
+
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
80
|
+
return trimmed ? { Authorization: `Token ${trimmed}` } : {};
|
|
81
|
+
}
|
|
82
|
+
/** true iff an LDA API key is configured (for the `_meta` note — never the value). */
|
|
83
|
+
export function ldaKeyPresent() {
|
|
84
|
+
const raw = process.env.LDA_API_KEY;
|
|
85
|
+
return typeof raw === "string" && raw.trim().length > 0;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The registrant/client value may be an OBJECT carrying `name` (with address
|
|
89
|
+
* fields) OR a plain string. Defensively resolve either to the display name (or
|
|
90
|
+
* null when absent) — never fabricate, never surface the whole address object.
|
|
91
|
+
*/
|
|
92
|
+
export function nameOf(x) {
|
|
93
|
+
if (x === null || x === undefined)
|
|
94
|
+
return null;
|
|
95
|
+
if (typeof x === "string")
|
|
96
|
+
return str(x);
|
|
97
|
+
if (typeof x === "object")
|
|
98
|
+
return str(x.name);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/** Map ONE lobbying_activities row → the curated shape (missing arrays ⇒ []). */
|
|
102
|
+
function mapActivity(raw) {
|
|
103
|
+
const a = (raw ?? {});
|
|
104
|
+
const entities = Array.isArray(a.government_entities)
|
|
105
|
+
? a.government_entities
|
|
106
|
+
.map((e) => str(e?.name))
|
|
107
|
+
.filter((n) => n !== null)
|
|
108
|
+
: [];
|
|
109
|
+
return {
|
|
110
|
+
issueCode: str(a.general_issue_code),
|
|
111
|
+
description: str(a.description),
|
|
112
|
+
governmentEntities: entities,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Map ONE `results[]` filing row → the curated LdaFiling shape. */
|
|
116
|
+
function mapFiling(raw) {
|
|
117
|
+
const f = (raw ?? {});
|
|
118
|
+
return {
|
|
119
|
+
filingUuid: str(f.filing_uuid),
|
|
120
|
+
filingType: str(f.filing_type),
|
|
121
|
+
filingYear: num(f.filing_year),
|
|
122
|
+
filingPeriod: str(f.filing_period_display) ?? str(f.filing_period),
|
|
123
|
+
// [P3] null (not reported) ⇒ null, NEVER 0; a genuine "0" ⇒ 0.
|
|
124
|
+
incomeUsd: num(f.income),
|
|
125
|
+
expensesUsd: num(f.expenses),
|
|
126
|
+
registrant: nameOf(f.registrant),
|
|
127
|
+
client: nameOf(f.client),
|
|
128
|
+
lobbyingActivities: Array.isArray(f.lobbying_activities)
|
|
129
|
+
? f.lobbying_activities.map(mapActivity)
|
|
130
|
+
: [],
|
|
131
|
+
documentUrl: str(f.filing_document_url),
|
|
132
|
+
postedDate: str(f.dt_posted),
|
|
133
|
+
terminationDate: str(f.termination_date),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Search US Senate LDA lobbying filings (`/api/v1/filings/`) → curated filing rows
|
|
138
|
+
* + honest `_meta`. KEYLESS (an optional LDA_API_KEY only raises the rate limit,
|
|
139
|
+
* sent as the Authorization header only). ★totalAvailable is the API's REAL `count`
|
|
140
|
+
* (~1.95M corpus) — never results.length; page-based pagination. income/expenses
|
|
141
|
+
* null-or-decimal-string → null-never-0.
|
|
142
|
+
*/
|
|
143
|
+
export async function searchFilings(args) {
|
|
144
|
+
// ── Validate + default (belt-and-suspenders behind the server Zod; a DIRECT
|
|
145
|
+
// handler call bypasses Zod). filingYear / page / pageSize are charclass/
|
|
146
|
+
// range-guarded; the free-text filters ride URLSearchParams (encoded). ──
|
|
147
|
+
if (args.filingYear !== undefined && !YEAR_RE.test(args.filingYear)) {
|
|
148
|
+
throw new ToolErrorCarrier({
|
|
149
|
+
kind: "invalid_input",
|
|
150
|
+
retryable: false,
|
|
151
|
+
message: `Invalid filingYear ${JSON.stringify(args.filingYear)} — expected a 4-digit year (^\\d{4}$), e.g. "2024".`,
|
|
152
|
+
upstreamEndpoint: LDA_FILINGS_LABEL,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const page = clampPage(args.page);
|
|
156
|
+
const pageSize = clampPageSize(args.pageSize);
|
|
157
|
+
// ── Build the query from VALIDATED typed args, key-by-key (SSRF: no raw
|
|
158
|
+
// passthrough; every VALUE is URLSearchParams-encoded). ──
|
|
159
|
+
const params = new URLSearchParams();
|
|
160
|
+
const filtersApplied = [];
|
|
161
|
+
const setFilter = (key, val, label) => {
|
|
162
|
+
if (val !== undefined && val !== "") {
|
|
163
|
+
params.set(key, val);
|
|
164
|
+
filtersApplied.push(label);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
setFilter("registrant_name", args.registrantName, "registrantName");
|
|
168
|
+
setFilter("client_name", args.clientName, "clientName");
|
|
169
|
+
setFilter("lobbyist_name", args.lobbyistName, "lobbyistName");
|
|
170
|
+
setFilter("filing_year", args.filingYear, "filingYear");
|
|
171
|
+
setFilter("filing_type", args.filingType, "filingType");
|
|
172
|
+
setFilter("government_entity", args.agency, "agency");
|
|
173
|
+
setFilter("filing_specific_lobbying_issues", args.issue, "issue");
|
|
174
|
+
params.set("page", String(page));
|
|
175
|
+
params.set("page_size", String(pageSize));
|
|
176
|
+
const url = `https://${LDA_HOST}${LDA_FILINGS_PATH}?${params.toString()}`;
|
|
177
|
+
// Belt-and-suspenders: the fixed host + strictly-built query leave nothing to
|
|
178
|
+
// steer the authority; assert the built URL cannot have been moved off-host.
|
|
179
|
+
const built = new URL(url);
|
|
180
|
+
if (built.hostname !== LDA_HOST || built.protocol !== "https:") {
|
|
181
|
+
throw new ToolErrorCarrier({
|
|
182
|
+
kind: "invalid_input",
|
|
183
|
+
retryable: false,
|
|
184
|
+
message: `Constructed LDA URL host ${JSON.stringify(built.hostname)} (${built.protocol}) is not ${LDA_HOST} over https — refusing to fetch (SSRF safety).`,
|
|
185
|
+
upstreamEndpoint: LDA_FILINGS_LABEL,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// ── Fetch through the shared envelope. The optional key rides the Authorization
|
|
189
|
+
// header ONLY (never the URL/label/_meta); redirect:"error" fails closed on
|
|
190
|
+
// any off-host 3xx. A 429 ⇒ rate_limited THROW (Retry-After honored by the
|
|
191
|
+
// shared taxonomy, never routed around); a 5xx/timeout ⇒ upstream_unavailable
|
|
192
|
+
// THROW; a 400 ⇒ invalid_input (re-read below to surface the API message); a
|
|
193
|
+
// 200 non-JSON ⇒ getJson's r.json() throws a SyntaxError ⇒ schema_drift. ──
|
|
194
|
+
const headers = ldaAuthHeader();
|
|
195
|
+
let body;
|
|
196
|
+
try {
|
|
197
|
+
body = await getJson(url, {
|
|
198
|
+
label: LDA_FILINGS_LABEL,
|
|
199
|
+
headers,
|
|
200
|
+
redirect: "error",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
if (e instanceof SyntaxError) {
|
|
205
|
+
throw driftError(LDA_FILINGS_LABEL, "LDA /api/v1/filings returned a non-JSON body at HTTP 200 — schema drift (never read as an empty result).");
|
|
206
|
+
}
|
|
207
|
+
// [P2] A 400 (bad filter/param) carries a DRF error body; fetchWithRetry
|
|
208
|
+
// discarded it. Re-read once on the error path ONLY so the caller learns the
|
|
209
|
+
// REAL reason (never a fake-empty). 5xx/429/404/timeout keep their taxonomy.
|
|
210
|
+
if (e instanceof ToolErrorCarrier && e.toolError.upstreamStatus === 400) {
|
|
211
|
+
const apiMsg = await readLdaErrorMessage(url, headers);
|
|
212
|
+
throw new ToolErrorCarrier({
|
|
213
|
+
kind: "invalid_input",
|
|
214
|
+
retryable: false,
|
|
215
|
+
message: apiMsg
|
|
216
|
+
? `LDA rejected the request (HTTP 400): ${apiMsg}. Check the filter parameters (filingYear, filingType, agency, issue, …).`
|
|
217
|
+
: "LDA rejected the request (HTTP 400) — check the filter parameters (filingYear, filingType, agency, issue, …).",
|
|
218
|
+
upstreamStatus: 400,
|
|
219
|
+
upstreamEndpoint: LDA_FILINGS_LABEL,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
throw e; // 5xx → upstream_unavailable, 404 → not_found, 429 → rate_limited …
|
|
223
|
+
}
|
|
224
|
+
// ── [P4] `results` MUST be an array and `count` MUST be a number (a missing/
|
|
225
|
+
// wrong-typed either is drift, never a fabricated empty/total). ──
|
|
226
|
+
const b = (body ?? {});
|
|
227
|
+
if (!Array.isArray(b.results)) {
|
|
228
|
+
throw driftError(LDA_FILINGS_LABEL, "LDA /api/v1/filings shape drift — `results` must be an array.");
|
|
229
|
+
}
|
|
230
|
+
if (typeof b.count !== "number" || !Number.isFinite(b.count)) {
|
|
231
|
+
throw driftError(LDA_FILINGS_LABEL, "LDA /api/v1/filings shape drift — `count` (the total match count) must be a number.");
|
|
232
|
+
}
|
|
233
|
+
const filings = b.results.map(mapFiling);
|
|
234
|
+
const returned = filings.length;
|
|
235
|
+
// ── [P1] totalAvailable is the API's REAL count (~1.95M), NEVER results.length.
|
|
236
|
+
// Page-based: hasMore = page*pageSize < count; the next page is surfaced. ──
|
|
237
|
+
const totalAvailable = b.count;
|
|
238
|
+
const offset = (page - 1) * pageSize;
|
|
239
|
+
const hasMore = page * pageSize < totalAvailable;
|
|
240
|
+
const nextOffset = hasMore ? page * pageSize : null;
|
|
241
|
+
const notes = [COUNT_TOTAL_NOTE, AMOUNT_NOTE, KEYLESS_NOTE];
|
|
242
|
+
notes.push(`LDA API key: ${ldaKeyPresent() ? "present (Authorization: Token … sent; value never logged)" : "absent (keyless; a free LDA_API_KEY lifts the rate limit)"}.`);
|
|
243
|
+
if (hasMore) {
|
|
244
|
+
notes.push(`This is page ${page} (pageSize ${pageSize}) of ~${Math.ceil(totalAvailable / pageSize)} — pass page=${page + 1} for the next page.`);
|
|
245
|
+
}
|
|
246
|
+
return withMeta({ filings }, {
|
|
247
|
+
source: `${LDA_HOST} /api/v1/filings (US Senate LDA lobbying; keyless)`,
|
|
248
|
+
keylessMode: true, // ★KEYLESS — the optional key only raises the rate limit
|
|
249
|
+
returned,
|
|
250
|
+
totalAvailable,
|
|
251
|
+
filtersApplied,
|
|
252
|
+
filtersDropped: [],
|
|
253
|
+
fieldsUnavailable: [],
|
|
254
|
+
pagination: { offset, limit: pageSize, hasMore, nextOffset },
|
|
255
|
+
notes,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Single bare GET to read an LDA 400's DRF error body (error path ONLY). Returns a
|
|
260
|
+
* compact human-readable message, or null on any failure. Sends the SAME headers
|
|
261
|
+
* (so a keyed re-read honors the key) + redirect:"error"; the key stays header-only.
|
|
262
|
+
*/
|
|
263
|
+
async function readLdaErrorMessage(url, headers) {
|
|
264
|
+
try {
|
|
265
|
+
const r = await fetch(url, {
|
|
266
|
+
signal: AbortSignal.timeout(15_000),
|
|
267
|
+
headers,
|
|
268
|
+
redirect: "error",
|
|
269
|
+
});
|
|
270
|
+
const body = (await r.json());
|
|
271
|
+
return summarizeDrfError(body);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Summarize a Django-REST-Framework 400 error body into a compact string. DRF
|
|
279
|
+
* emits `{ detail: "…" }` OR `{ field: ["message", …], … }`. Returns null for a
|
|
280
|
+
* shape we can't read (⇒ the caller falls back to the generic 400 message).
|
|
281
|
+
*/
|
|
282
|
+
function summarizeDrfError(body) {
|
|
283
|
+
if (typeof body === "string")
|
|
284
|
+
return str(body);
|
|
285
|
+
if (body === null || typeof body !== "object")
|
|
286
|
+
return null;
|
|
287
|
+
const obj = body;
|
|
288
|
+
if (typeof obj.detail === "string")
|
|
289
|
+
return str(obj.detail);
|
|
290
|
+
const parts = [];
|
|
291
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
292
|
+
const msg = Array.isArray(v)
|
|
293
|
+
? v.map((x) => str(x)).filter((x) => x !== null).join("; ")
|
|
294
|
+
: str(v);
|
|
295
|
+
if (msg)
|
|
296
|
+
parts.push(`${k}: ${msg}`);
|
|
297
|
+
}
|
|
298
|
+
return parts.length > 0 ? parts.join(" | ") : null;
|
|
299
|
+
}
|
|
300
|
+
// ─── Small clamps (defensive, behind the server Zod bounds) ────────
|
|
301
|
+
function clampPage(v) {
|
|
302
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
303
|
+
return DEFAULT_PAGE;
|
|
304
|
+
const n = Math.floor(v);
|
|
305
|
+
return n < 1 ? 1 : n;
|
|
306
|
+
}
|
|
307
|
+
function clampPageSize(v) {
|
|
308
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
309
|
+
return DEFAULT_PAGE_SIZE;
|
|
310
|
+
const n = Math.floor(v);
|
|
311
|
+
if (n < 1)
|
|
312
|
+
return 1;
|
|
313
|
+
if (n > MAX_PAGE_SIZE)
|
|
314
|
+
return MAX_PAGE_SIZE;
|
|
315
|
+
return n;
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=lda.js.map
|
package/dist/lda.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lda.js","sourceRoot":"","sources":["../src/lda.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,YAAY,EAKb,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,YAAY,EAKb,MAAM,oBAAoB,CAAC;AAimH5B,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC;IAO1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE;QAAE,GAAG,EAAE,YAAY,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE,CAAC;AAoBF,eAAO,MAAM,KAAK,EAAE,OAAO,EAkgD1B,CAAC;AA6IF,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB"}
|
package/dist/server.js
CHANGED
|
@@ -46,6 +46,10 @@ import * as clinicaltrials from "./clinicaltrials.js";
|
|
|
46
46
|
import * as census from "./census.js";
|
|
47
47
|
import * as censusEconomic from "./census-economic.js";
|
|
48
48
|
import * as fred from "./fred.js";
|
|
49
|
+
import * as bea from "./bea.js";
|
|
50
|
+
import * as gsaPerdiem from "./gsa-perdiem.js";
|
|
51
|
+
import * as dol from "./dol.js";
|
|
52
|
+
import * as lda from "./lda.js";
|
|
49
53
|
import * as fema from "./fema.js";
|
|
50
54
|
import * as fdic from "./fdic.js";
|
|
51
55
|
import * as bls from "./bls.js";
|
|
@@ -64,7 +68,7 @@ import { realpathSync } from "node:fs";
|
|
|
64
68
|
const SERVER_NAME = "mcp-sam-gov";
|
|
65
69
|
// Kept in lockstep with package.json / manifest.json / server.json.
|
|
66
70
|
// Keep in sync with package.json "version" (asserted at release; see CHANGELOG).
|
|
67
|
-
const SERVER_VERSION = "1.
|
|
71
|
+
const SERVER_VERSION = "1.3.0";
|
|
68
72
|
// ─── Tool input schemas (Zod) ────────────────────────────────────
|
|
69
73
|
const SamSearchInput = z.object({
|
|
70
74
|
query: z.string().optional().describe("Free-text title query"),
|
|
@@ -2876,6 +2880,192 @@ const FredSeriesObservationsInput = z.object({
|
|
|
2876
2880
|
.optional()
|
|
2877
2881
|
.describe("Observation date order: 'asc' (oldest first, FRED default) or 'desc' (newest first)."),
|
|
2878
2882
|
});
|
|
2883
|
+
// ─── BEA Regional Economic Accounts (apps.bea.gov) — the THIRD key-required source ──
|
|
2884
|
+
// ADR-0051. County/state/MSA GDP-by-industry (CAGDP2/SAGDP2N) + personal income
|
|
2885
|
+
// (CAINC1/SAINC1) — the regional/sub-national place-of-performance lane. REQUIRES a
|
|
2886
|
+
// free BEA_API_KEY; without it the tool throws an honest config error (the other 116
|
|
2887
|
+
// tools stay keyless). ★A missing/invalid key returns HTTP 200 with a
|
|
2888
|
+
// BEAAPI.Results.Error carrier (NOT an HTTP error), detected pre-drift. The key rides
|
|
2889
|
+
// UserID= ONLY. DataValue is a comma string; suppression codes ((NA)/(D)/…) → null.
|
|
2890
|
+
const BeaRegionalDataInput = z.object({
|
|
2891
|
+
tableName: z
|
|
2892
|
+
.string()
|
|
2893
|
+
.regex(/^[A-Za-z0-9]{2,20}$/)
|
|
2894
|
+
.describe("A BEA Regional table code (2–20 alphanumerics), e.g. 'CAGDP2' (county GDP by industry), 'SAGDP2N' (state GDP by industry), 'CAINC1'/'SAINC1' (personal income). Validated ^[A-Za-z0-9]{2,20}$. Required."),
|
|
2895
|
+
geoFips: z
|
|
2896
|
+
.string()
|
|
2897
|
+
.regex(/^[A-Za-z0-9]{2,10}$/)
|
|
2898
|
+
.describe("The BEA GeoFips selector: 'STATE' (all states), a county FIPS like '06075', or an MSA code. Validated ^[A-Za-z0-9]{2,10}$. Required."),
|
|
2899
|
+
lineCode: z
|
|
2900
|
+
.string()
|
|
2901
|
+
.regex(/^([0-9]{1,4}|ALL)$/)
|
|
2902
|
+
.describe("The industry/statistic line code — an integer (1–4 digits), e.g. '1', or 'ALL' for every line in the table. Validated ^([0-9]{1,4}|ALL)$. Required."),
|
|
2903
|
+
year: z
|
|
2904
|
+
.string()
|
|
2905
|
+
.regex(/^(\d{4}|LAST5|ALL)$/)
|
|
2906
|
+
.optional()
|
|
2907
|
+
.describe("The data year: a 4-digit year (e.g. '2022'), 'LAST5' (the latest 5 years, default), or 'ALL'. Validated ^(\\d{4}|LAST5|ALL)$."),
|
|
2908
|
+
frequency: z
|
|
2909
|
+
.enum(["A", "Q"])
|
|
2910
|
+
.optional()
|
|
2911
|
+
.describe("Data frequency: 'A' (annual, default) or 'Q' (quarterly)."),
|
|
2912
|
+
});
|
|
2913
|
+
// ─── GSA Federal Travel Per-Diem (api.gsa.gov) — travel-cost lane ──
|
|
2914
|
+
// ADR-0050. Lodging + M&IE reimbursement ceilings by city/state OR zip for a year.
|
|
2915
|
+
// KEYLESS by default via the shared DEMO_KEY (datagovKey.ts seam); DATA_GOV_API_KEY
|
|
2916
|
+
// lifts the rate. EITHER (city+state) OR zip — both/neither ⇒ invalid_input, 0 fetch.
|
|
2917
|
+
const GsaPerdiemRatesInput = z
|
|
2918
|
+
.object({
|
|
2919
|
+
city: z
|
|
2920
|
+
.string()
|
|
2921
|
+
.regex(/^[A-Za-z .'\-]{1,60}$/)
|
|
2922
|
+
.optional()
|
|
2923
|
+
.describe("The city name (e.g. 'Washington', 'San Francisco'). Requires `state`. Validated ^[A-Za-z .'\\-]{1,60}$. Use EITHER (city + state) OR zip — not both."),
|
|
2924
|
+
state: z
|
|
2925
|
+
.string()
|
|
2926
|
+
.regex(/^[A-Za-z]{2}$/)
|
|
2927
|
+
.optional()
|
|
2928
|
+
.describe("The 2-letter state/territory code (e.g. 'DC', 'CA'). Required with `city`. Validated ^[A-Za-z]{2}$."),
|
|
2929
|
+
zip: z
|
|
2930
|
+
.string()
|
|
2931
|
+
.regex(/^\d{5}$/)
|
|
2932
|
+
.optional()
|
|
2933
|
+
.describe("A 5-digit ZIP code (e.g. '20001'). The alternative lookup mode to city+state. Validated ^\\d{5}$. Use EITHER zip OR (city + state) — not both."),
|
|
2934
|
+
year: z
|
|
2935
|
+
.string()
|
|
2936
|
+
.regex(/^\d{4}$/)
|
|
2937
|
+
.optional()
|
|
2938
|
+
.describe("The per-diem fiscal year (default '2025'). Validated ^\\d{4}$ (it rides in the request path)."),
|
|
2939
|
+
})
|
|
2940
|
+
.describe("Look up GSA per-diem rates by EITHER (city + state) OR zip. Supplying both, or neither, ⇒ invalid_input.");
|
|
2941
|
+
// ─── US DOL Data API v4 (apiprod.dol.gov) — the labor-enforcement lane ──
|
|
2942
|
+
// ADR-0053. A DELIBERATE key split: dol_list_datasets (the CATALOG) is KEYLESS;
|
|
2943
|
+
// dol_get_dataset (the DATA endpoint) is the 4th REQUIRED key (DOL_API_KEY, no keyless
|
|
2944
|
+
// tier — throws pre-fetch without it). The key rides the X-API-KEY HEADER ONLY. The
|
|
2945
|
+
// data envelope is key-gated/unverified ⇒ records are surfaced verbatim + totalAvailable
|
|
2946
|
+
// defaults null (never `returned` faked as the total). agency/query filter is CLIENT-SIDE.
|
|
2947
|
+
const DolListDatasetsInput = z.object({
|
|
2948
|
+
agency: z
|
|
2949
|
+
.string()
|
|
2950
|
+
.min(1)
|
|
2951
|
+
.max(100)
|
|
2952
|
+
.optional()
|
|
2953
|
+
.describe("CLIENT-SIDE filter by agency abbreviation (e.g. 'WHD', 'OSHA', 'ILAB', 'ETA') or a substring of the agency name. The DOL catalog API does not filter server-side, so this is applied to the fetched catalog."),
|
|
2954
|
+
query: z
|
|
2955
|
+
.string()
|
|
2956
|
+
.min(1)
|
|
2957
|
+
.max(200)
|
|
2958
|
+
.optional()
|
|
2959
|
+
.describe("CLIENT-SIDE free-text filter (substring over dataset name / description / category / table / endpoint), e.g. 'child labor', 'wage', 'inspection'."),
|
|
2960
|
+
limit: z
|
|
2961
|
+
.number()
|
|
2962
|
+
.int()
|
|
2963
|
+
.min(1)
|
|
2964
|
+
.max(200)
|
|
2965
|
+
.optional()
|
|
2966
|
+
.describe("Datasets to return per page (default 25, max 200). Offset-paginated over the (filtered) catalog."),
|
|
2967
|
+
offset: z
|
|
2968
|
+
.number()
|
|
2969
|
+
.int()
|
|
2970
|
+
.min(0)
|
|
2971
|
+
.optional()
|
|
2972
|
+
.describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
|
|
2973
|
+
});
|
|
2974
|
+
const DolGetDatasetInput = z.object({
|
|
2975
|
+
agency: z
|
|
2976
|
+
.string()
|
|
2977
|
+
.regex(/^[A-Za-z0-9_]+$/)
|
|
2978
|
+
.describe("The agency abbreviation (the `agencyAbbr` from dol_list_datasets), e.g. 'WHD', 'OSHA', 'ILAB'. Rides in the request PATH. Validated ^[A-Za-z0-9_]+$. Required."),
|
|
2979
|
+
table: z
|
|
2980
|
+
.string()
|
|
2981
|
+
.regex(/^[A-Za-z0-9_]+$/)
|
|
2982
|
+
.describe("The dataset endpoint — the `apiUrl` field from dol_list_datasets (the DOL 'api_url', NOT the tablename), e.g. 'Child_Labor_Report__2016_to_2022'. Rides in the request PATH. Validated ^[A-Za-z0-9_]+$. Required."),
|
|
2983
|
+
limit: z
|
|
2984
|
+
.number()
|
|
2985
|
+
.int()
|
|
2986
|
+
.min(1)
|
|
2987
|
+
.max(100)
|
|
2988
|
+
.optional()
|
|
2989
|
+
.describe("Max records to return (default 10, max 100). Offset-paginated."),
|
|
2990
|
+
offset: z
|
|
2991
|
+
.number()
|
|
2992
|
+
.int()
|
|
2993
|
+
.min(0)
|
|
2994
|
+
.optional()
|
|
2995
|
+
.describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
|
|
2996
|
+
filterField: z
|
|
2997
|
+
.string()
|
|
2998
|
+
.min(1)
|
|
2999
|
+
.max(100)
|
|
3000
|
+
.optional()
|
|
3001
|
+
.describe("Optional: a dataset field name to filter on (paired with filterValue → a DOL filter_object equality filter). Supply BOTH or NEITHER."),
|
|
3002
|
+
filterValue: z
|
|
3003
|
+
.string()
|
|
3004
|
+
.min(1)
|
|
3005
|
+
.max(200)
|
|
3006
|
+
.optional()
|
|
3007
|
+
.describe("Optional: the value the filterField must equal. Supply BOTH filterField and filterValue, or NEITHER."),
|
|
3008
|
+
fields: z
|
|
3009
|
+
.array(z.string().min(1))
|
|
3010
|
+
.optional()
|
|
3011
|
+
.describe("Optional: best-effort column selection (a subset of field names to return). Not documented for v4; the API ignores or 400s an unsupported selection (surfaced honestly)."),
|
|
3012
|
+
});
|
|
3013
|
+
// ─── US Senate LDA lobbying filings (lda.senate.gov) — the lobbying/B2G lane ──
|
|
3014
|
+
// ADR-0052. Who is paid HOW MUCH to lobby WHICH federal agency on WHICH issue.
|
|
3015
|
+
// KEYLESS (anonymous 200); an optional free LDA_API_KEY only raises the rate limit
|
|
3016
|
+
// and rides the Authorization: Token … header ONLY. `count` is the REAL total
|
|
3017
|
+
// (~1.95M) — never results.length; page-based pagination (page/pageSize ≤25). All
|
|
3018
|
+
// filter VALUES ride URLSearchParams; filingYear/page/pageSize charclass/range-guarded.
|
|
3019
|
+
const LdaSearchFilingsInput = z.object({
|
|
3020
|
+
registrantName: z
|
|
3021
|
+
.string()
|
|
3022
|
+
.min(1)
|
|
3023
|
+
.optional()
|
|
3024
|
+
.describe("Filter by the registrant (the lobbying firm / in-house filer) name, e.g. 'Akin Gump'. Substring match, upstream-validated."),
|
|
3025
|
+
clientName: z
|
|
3026
|
+
.string()
|
|
3027
|
+
.min(1)
|
|
3028
|
+
.optional()
|
|
3029
|
+
.describe("Filter by the client name (who the lobbying is FOR), e.g. 'Google'. Substring match, upstream-validated."),
|
|
3030
|
+
lobbyistName: z
|
|
3031
|
+
.string()
|
|
3032
|
+
.min(1)
|
|
3033
|
+
.optional()
|
|
3034
|
+
.describe("Filter by an individual lobbyist's name. Substring match, upstream-validated."),
|
|
3035
|
+
filingYear: z
|
|
3036
|
+
.string()
|
|
3037
|
+
.regex(/^\d{4}$/)
|
|
3038
|
+
.optional()
|
|
3039
|
+
.describe("Filter by filing year, a 4-digit year (e.g. '2024'). Validated ^\\d{4}$."),
|
|
3040
|
+
filingType: z
|
|
3041
|
+
.string()
|
|
3042
|
+
.min(1)
|
|
3043
|
+
.optional()
|
|
3044
|
+
.describe("Filter by the filing type short code (e.g. 'Q1' Q1 report, 'RR' registration, 'YE' year-end). A bad code ⇒ upstream HTTP 400 ⇒ invalid_input (surfaced)."),
|
|
3045
|
+
agency: z
|
|
3046
|
+
.string()
|
|
3047
|
+
.min(1)
|
|
3048
|
+
.optional()
|
|
3049
|
+
.describe("Filter by the federal government entity lobbied (maps to government_entity — the B2G signal), e.g. 'DEPARTMENT OF DEFENSE'."),
|
|
3050
|
+
issue: z
|
|
3051
|
+
.string()
|
|
3052
|
+
.min(1)
|
|
3053
|
+
.optional()
|
|
3054
|
+
.describe("Filter by the specific lobbying issues text (maps to filing_specific_lobbying_issues), e.g. 'appropriations'."),
|
|
3055
|
+
page: z
|
|
3056
|
+
.number()
|
|
3057
|
+
.int()
|
|
3058
|
+
.min(1)
|
|
3059
|
+
.default(1)
|
|
3060
|
+
.describe("1-based page number (default 1). Page with the next page number from _meta.notes / when _meta.pagination.hasMore."),
|
|
3061
|
+
pageSize: z
|
|
3062
|
+
.number()
|
|
3063
|
+
.int()
|
|
3064
|
+
.min(1)
|
|
3065
|
+
.max(25)
|
|
3066
|
+
.default(25)
|
|
3067
|
+
.describe("Filings per page, 1..25 (the LDA API caps at 25), default 25."),
|
|
3068
|
+
});
|
|
2879
3069
|
// api_key_status takes no input — it is a pure status query over process.env.
|
|
2880
3070
|
const ApiKeyStatusInput = z.object({});
|
|
2881
3071
|
// Build a ToolDef whose `handler` is type-checked against the schema's inferred
|
|
@@ -4168,14 +4358,73 @@ export const TOOLS = [
|
|
|
4168
4358
|
inputSchema: FredSeriesObservationsInput,
|
|
4169
4359
|
handler: (input) => fred.seriesObservations(input),
|
|
4170
4360
|
}),
|
|
4361
|
+
// ━━━ BEA Regional Economic Accounts (apps.bea.gov) — regional GDP/income (1) ━━━ ADR-0051
|
|
4362
|
+
// ★The server's THIRD KEY-REQUIRED source: the BEA Data API has NO keyless tier, so
|
|
4363
|
+
// WITHOUT a BEA_API_KEY this tool throws an honest invalid_input config error (the
|
|
4364
|
+
// other 116 tools stay keyless). County/state/MSA GDP-by-industry + personal income —
|
|
4365
|
+
// the regional place-of-performance lane. ★The P2 crux: a missing/invalid key returns
|
|
4366
|
+
// HTTP 200 carrying BEAAPI.Results.Error (NOT an HTTP error status), which is detected
|
|
4367
|
+
// BEFORE the Data-array drift check and surfaced as invalid_input (never a fake empty).
|
|
4368
|
+
// DataValue is a comma-formatted string; suppression codes ((NA)/(D)/(NM)/(L)/*) → null.
|
|
4369
|
+
defineTool({
|
|
4370
|
+
name: "bea_regional_data",
|
|
4371
|
+
description: "Regional (county / state / MSA) economic data — GDP by industry and personal income — from the US Bureau of Economic Analysis (BEA) Regional Economic Accounts (apps.bea.gov/api/data, dataset 'Regional'). ★REQUIRES a free BEA_API_KEY: the BEA Data API has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://apps.bea.gov/API/signup/; Census, FRED, and BEA are the only key-required sources — every other tool is keyless). Input: `tableName` (required, e.g. 'CAGDP2' county GDP by industry, 'SAGDP2N' state GDP, 'CAINC1'/'SAINC1' personal income), `geoFips` (required — 'STATE' for all states, a county FIPS like '06075', or an MSA code), `lineCode` (required — an integer industry line like '1', or 'ALL'), optional `year` ('LAST5' default, a 4-digit year, or 'ALL'), `frequency` ('A' annual default, or 'Q'). Returns { rows:[{ geoFips, geoName, timePeriod, lineCode, dataValue, unitOfMeasure, unitMult, noteRef }], notes:[{ noteRef, noteText }] } + honest _meta. ★HONESTY (the crux): a missing/invalid key — or ANY bad parameter — returns HTTP 200 carrying an Error object (NOT an HTTP error status); this is detected and surfaced as invalid_input carrying BEA's APIErrorDescription — NEVER a fake empty. dataValue is parsed from BEA's comma-formatted string ('1,234,567'→1234567); BEA suppression/not-available codes ((NA)/(D)/(NM)/(L)/*) map to null — NEVER 0 (a genuine 0 stays 0). unitMult (a power-of-10 multiplier) and unitOfMeasure are reported ALONGSIDE the raw dataValue — the value is NOT multiplied in (apply unitMult yourself). BEA returns the COMPLETE set for the filter (no pagination) ⇒ totalAvailable = the row count, complete:true; a genuine empty Data:[] ⇒ honest empty (returned:0); a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The key rides ONLY in the UserID= query param — never logged or echoed.",
|
|
4372
|
+
inputSchema: BeaRegionalDataInput,
|
|
4373
|
+
handler: (input) => bea.regionalData(input),
|
|
4374
|
+
}),
|
|
4375
|
+
// ━━━ GSA Federal Travel Per-Diem (api.gsa.gov) — travel-cost lane (1) ━━━ ADR-0050
|
|
4376
|
+
// The lodging + M&IE reimbursement ceilings the federal government pays for official
|
|
4377
|
+
// travel, by city/state OR zip for a year. SAME host (api.gsa.gov) + SAME api.data.gov
|
|
4378
|
+
// key seam (datagovKey.ts, X-Api-Key header) as datagov_search_datasets — KEYLESS by
|
|
4379
|
+
// default via the shared DEMO_KEY, keylessMode:false. EITHER (city+state) OR zip; both
|
|
4380
|
+
// or neither ⇒ invalid_input, 0 fetch. `value` (monthly lodging) / `meals` are null-
|
|
4381
|
+
// never-0; standardRate/isOconus are STRING booleans coerced to real booleans.
|
|
4382
|
+
defineTool({
|
|
4383
|
+
name: "gsa_perdiem_rates",
|
|
4384
|
+
description: "Look up GSA Federal Travel PER-DIEM rates — the max lodging + Meals & Incidental Expenses (M&IE) reimbursement ceilings for official U.S. government travel (api.gsa.gov /travel/perdiem/v2, keyed — DATA_GOV_API_KEY or the shared DEMO_KEY). Input: EITHER `city` (e.g. 'Washington') + `state` (2-letter, e.g. 'DC') OR `zip` (5-digit) — supplying BOTH, or NEITHER, ⇒ invalid_input with 0 fetch; optional `year` (default '2025'). Returns { rates:[{ city, county, state, zip, year, isOconus, standardRate, mealsUsd, monthlyLodgingUsd:[{ month (1-12), monthName, lodgingUsd }] }] } + honest _meta. HONESTY: lodgingUsd (the API's monthly `value`) is the MAX nightly lodging ceiling for that month — it VARIES SEASONALLY (hence a per-month array), and mealsUsd is the daily M&IE ceiling; both are integer US dollars, null-when-withheld (NEVER 0 — a genuine 0 is preserved). standardRate/isOconus are booleans coerced from the API's string 'true'/'false' (an unrecognized value ⇒ null, never a fabricated false); the months array is preserved AS-IS (never padded to 12). The API returns the COMPLETE rate set (no pagination) ⇒ totalAvailable = the row count, complete:true. A genuine no-match (rates:[]/rate:[]) ⇒ honest empty (returned:0); the API's `errors` field non-null ⇒ invalid_input carrying the message (never a fake empty); a 429 (DEMO_KEY ~10 req/hr, hit quickly) ⇒ rate_limited THROWS; a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON ⇒ schema_drift. DEMO_KEY ~10 req/hr shared ceiling — set DATA_GOV_API_KEY (free at api.data.gov/signup) for 1000/hr. The key rides ONLY in the X-Api-Key header (never the URL/_meta).",
|
|
4385
|
+
inputSchema: GsaPerdiemRatesInput,
|
|
4386
|
+
handler: (input) => gsaPerdiem.perdiemRates(input),
|
|
4387
|
+
}),
|
|
4388
|
+
// ━━━ US DOL Data API v4 (apiprod.dol.gov) — the labor-enforcement lane (2) ━━━ ADR-0053
|
|
4389
|
+
// A DELIBERATE key split: dol_list_datasets (the dataset CATALOG) is KEYLESS;
|
|
4390
|
+
// dol_get_dataset (the DATA endpoint) is the server's 4th REQUIRED key (DOL_API_KEY —
|
|
4391
|
+
// the data endpoint has NO keyless tier, so without the key it THROWS pre-fetch). The
|
|
4392
|
+
// key rides the X-API-KEY HEADER ONLY. The data-record envelope is key-gated/unverified
|
|
4393
|
+
// ⇒ records are surfaced VERBATIM + totalAvailable defaults null (never `returned` faked
|
|
4394
|
+
// as the total). agency/query filtering on the catalog is CLIENT-SIDE.
|
|
4395
|
+
defineTool({
|
|
4396
|
+
name: "dol_list_datasets",
|
|
4397
|
+
description: "List the US Department of Labor Data API v4 dataset catalog (apiprod.dol.gov /v4/datasets) — the machine inventory of DOL enforcement/statistics datasets (WHD wage & hour, OSHA inspections, ILAB child/forced-labor reports, MSHA mine safety, ETA …). KEYLESS: the catalog needs NO API key (only dol_get_dataset does). Input (all optional): `agency` (CLIENT-SIDE filter by agency abbreviation like 'WHD'/'OSHA'/'ILAB', or an agency-name substring), `query` (CLIENT-SIDE free-text substring over dataset name/description/category/table/endpoint), `limit` (default 25, max 200), `offset`. Returns { datasets:[{ name, tablename, apiUrl, agency, agencyAbbr, description, frequency, datasetType, category }] } + honest _meta. ★Feed a row's `apiUrl` (the DOL 'api_url' endpoint) + its `agencyAbbr` into dol_get_dataset to fetch that dataset's records. HONESTY: agency/query filtering is CLIENT-SIDE (the DOL catalog API does not filter server-side, verified live); totalAvailable is the catalog's REAL total (meta.total_count) for an unfiltered scan, or the exact filtered-set size (the whole catalog is fetched in one page); offset pagination. Every scalar is null-never-empty-string. A non-array `datasets` / 200 non-JSON ⇒ schema_drift; a 5xx ⇒ THROWS.",
|
|
4398
|
+
inputSchema: DolListDatasetsInput,
|
|
4399
|
+
handler: (input) => dol.listDatasets(input),
|
|
4400
|
+
}),
|
|
4401
|
+
defineTool({
|
|
4402
|
+
name: "dol_get_dataset",
|
|
4403
|
+
description: "Fetch records from ONE US DOL dataset (apiprod.dol.gov /v4/get/{agency}/{endpoint}/json). ★REQUIRES a free DOL_API_KEY: the DOL DATA endpoint has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://dol.gov/developer; the dataset CATALOG — dol_list_datasets — and agency list stay keyless). Input: `agency` (required — the `agencyAbbr` from dol_list_datasets, e.g. 'WHD', 'OSHA', 'ILAB'; rides the PATH, ^[A-Za-z0-9_]+$), `table` (required — the dataset's `apiUrl` endpoint from dol_list_datasets, e.g. 'Child_Labor_Report__2016_to_2022'; rides the PATH, ^[A-Za-z0-9_]+$), optional `limit` (default 10, max 100), `offset`, `filterField`+`filterValue` (a paired equality filter → a DOL filter_object), `fields` (best-effort column selection). Returns { records:[…verbatim dataset rows…] } + honest _meta. HONESTY: records are surfaced VERBATIM (the data-record envelope is key-gated and unverified, so field names/values are preserved as-is — a genuine 0 stays 0, a missing field stays null; the tool never coerces or fabricates). totalAvailable is a real count field ONLY when the response carries one, else null (an honest unknown — `returned` is NEVER passed off as the total); offset pagination (a full page ⇒ hasMore, page forward to confirm). A missing/invalid key (401/403) ⇒ invalid_input carrying the DOL_API_KEY guidance (never empty); a 400 ⇒ invalid_input; a genuine empty ⇒ honest empty (returned:0); a 429 ⇒ rate_limited THROWS (Retry-After honored); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / no row array ⇒ schema_drift. The key rides ONLY in the X-API-KEY request header — never the URL / _meta / a log.",
|
|
4404
|
+
inputSchema: DolGetDatasetInput,
|
|
4405
|
+
handler: (input) => dol.getDataset(input),
|
|
4406
|
+
}),
|
|
4407
|
+
// ━━━ US Senate LDA lobbying filings (lda.senate.gov) — the lobbying/B2G lane (1) ━━━ ADR-0052
|
|
4408
|
+
// Who is paid HOW MUCH to lobby WHICH federal agency on WHICH issue — the
|
|
4409
|
+
// registrant→client→government-entity signal no contract/spending source carries.
|
|
4410
|
+
// KEYLESS (anonymous 200); the OPTIONAL free LDA_API_KEY only raises the rate limit
|
|
4411
|
+
// and rides the Authorization: Token … header ONLY (the socrata app-token lineage —
|
|
4412
|
+
// NOT key-required). ★count is the API's REAL total (~1.95M) — never results.length;
|
|
4413
|
+
// page-based pagination. income/expenses are null-or-decimal-string ⇒ null-never-0.
|
|
4414
|
+
defineTool({
|
|
4415
|
+
name: "lda_search_filings",
|
|
4416
|
+
description: "Search US Senate LDA (Lobbying Disclosure Act) filings — who is paid HOW MUCH to lobby WHICH federal agency on WHICH issue (lda.senate.gov/api/v1/filings, KEYLESS — anonymous access works; an optional free LDA_API_KEY only raises the rate limit). All inputs optional: `registrantName` (the lobbying firm/in-house filer), `clientName` (who it's for), `lobbyistName`, `filingYear` (4-digit), `filingType` (short code, e.g. 'Q1'/'RR'/'YE'), `agency` (the federal government_entity lobbied — the B2G signal), `issue` (specific lobbying issues text), `page` (1-based, default 1), `pageSize` (1..25, default 25). Returns { filings:[{ filingUuid, filingType, filingYear, filingPeriod, incomeUsd, expensesUsd, registrant, client, lobbyingActivities:[{ issueCode, description, governmentEntities:[names] }], documentUrl, postedDate, terminationDate }] } + honest _meta. HONESTY: totalAvailable is the API's REAL total match count (the corpus is ~1.95M filings) — NOT the rows on this page; pagination is page-based (pass the next page number when hasMore). incomeUsd/expensesUsd are parsed from the null-or-decimal-string income/expenses — null (not reported) ⇒ null, NEVER 0 (a genuine 0 stays 0); a filing reports EITHER income OR expenses, so the other is typically null. Missing lobbying_activities/government_entities ⇒ empty arrays (never fabricated). A genuine no-match (results:[]) ⇒ honest empty (returned:0); a 400 (bad filter) ⇒ invalid_input surfacing the API's message; a 429 ⇒ rate_limited THROWS (Retry-After honored, never routed around); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / non-array results / non-number count ⇒ schema_drift. The optional key rides ONLY in the Authorization: Token header (never the URL/_meta).",
|
|
4417
|
+
inputSchema: LdaSearchFilingsInput,
|
|
4418
|
+
handler: (input) => lda.searchFilings(input),
|
|
4419
|
+
}),
|
|
4171
4420
|
// ━━━ Self-service key discovery (1) ━━━
|
|
4172
4421
|
// KEYLESS. A local status query — reads process.env (+ any .env auto-loaded at
|
|
4173
4422
|
// startup) and reports, per key, whether it is set (a BOOLEAN — the key VALUE is
|
|
4174
|
-
// NEVER read into the output). Makes the
|
|
4423
|
+
// NEVER read into the output). Makes the 4-required + 6-optional key situation
|
|
4175
4424
|
// discoverable without reading source or docs.
|
|
4176
4425
|
defineTool({
|
|
4177
4426
|
name: "api_key_status",
|
|
4178
|
-
description: "List every API key this server can use, whether each is REQUIRED or OPTIONAL, the free signup URL + what it unlocks, and whether it is CURRENTLY configured — a boolean only; the key VALUE is NEVER shown. KEYLESS (no input). Most sources are keyless;
|
|
4427
|
+
description: "List every API key this server can use, whether each is REQUIRED or OPTIONAL, the free signup URL + what it unlocks, and whether it is CURRENTLY configured — a boolean only; the key VALUE is NEVER shown. KEYLESS (no input). Most sources are keyless; four sources need a key — Census (census_business_patterns), FRED (2 tools), and BEA (bea_regional_data) require one outright, and DOL's DATA endpoint (dol_get_dataset) needs one too (its catalog, dol_list_datasets, stays keyless) — the other 6 keys are OPTIONAL (raise a rate limit or unlock one filter). Keys can be set as host env vars OR in a `.env` file in the server's working directory (auto-loaded at startup; real env wins over .env). Returns { keys:[{ envVar, sources[], required, signupUrl, unlocks, note, currentlySet }], requiredMissing:[envVars], optionalMissing:[envVars], allKeysFree:true }. This tool tells you the CONFIG state; to verify a key actually WORKS, call that source's own tool. Getting a key (creating the account at the signup URL) is your step — the server automates discovery + configuration, not signup.",
|
|
4179
4428
|
inputSchema: ApiKeyStatusInput,
|
|
4180
4429
|
handler: async () => keys.apiKeyStatus(),
|
|
4181
4430
|
}),
|