@cliwant/mcp-sam-gov 1.4.0 → 1.6.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/LICENSE +21 -21
- package/README.ja.md +240 -231
- package/README.ko.md +240 -231
- package/README.md +725 -706
- package/dist/cbp-border.d.ts +51 -0
- package/dist/cbp-border.d.ts.map +1 -0
- package/dist/cbp-border.js +123 -0
- package/dist/cbp-border.js.map +1 -0
- package/dist/datagov-catalog.d.ts.map +1 -1
- package/dist/datagov-catalog.js +16 -2
- package/dist/datagov-catalog.js.map +1 -1
- package/dist/ecfr.d.ts +2 -2
- package/dist/ecfr.d.ts.map +1 -1
- package/dist/ecfr.js +24 -10
- package/dist/ecfr.js.map +1 -1
- package/dist/edgar.d.ts.map +1 -1
- package/dist/edgar.js +26 -6
- package/dist/edgar.js.map +1 -1
- package/dist/epa-envirofacts.d.ts.map +1 -1
- package/dist/epa-envirofacts.js +14 -1
- package/dist/epa-envirofacts.js.map +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -1
- package/dist/far.d.ts.map +1 -1
- package/dist/far.js +3 -1
- package/dist/far.js.map +1 -1
- package/dist/federal-register.d.ts +2 -2
- package/dist/federal-register.d.ts.map +1 -1
- package/dist/federal-register.js +26 -10
- package/dist/federal-register.js.map +1 -1
- package/dist/feedback.d.ts +64 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +131 -0
- package/dist/feedback.js.map +1 -0
- package/dist/fema.d.ts +36 -0
- package/dist/fema.d.ts.map +1 -1
- package/dist/fema.js +124 -0
- package/dist/fema.js.map +1 -1
- package/dist/gov-domains.d.ts +66 -0
- package/dist/gov-domains.d.ts.map +1 -0
- package/dist/gov-domains.js +211 -0
- package/dist/gov-domains.js.map +1 -0
- package/dist/nist-controls.d.ts +48 -0
- package/dist/nist-controls.d.ts.map +1 -0
- package/dist/nist-controls.js +174 -0
- package/dist/nist-controls.js.map +1 -0
- package/dist/nws-weather.d.ts +57 -0
- package/dist/nws-weather.d.ts.map +1 -0
- package/dist/nws-weather.js +131 -0
- package/dist/nws-weather.js.map +1 -0
- package/dist/openfda-drugsfda.d.ts +72 -0
- package/dist/openfda-drugsfda.d.ts.map +1 -0
- package/dist/openfda-drugsfda.js +230 -0
- package/dist/openfda-drugsfda.js.map +1 -0
- package/dist/openfda.d.ts.map +1 -1
- package/dist/openfda.js +31 -8
- package/dist/openfda.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +374 -11
- package/dist/server.js.map +1 -1
- package/dist/treasury.d.ts +2 -0
- package/dist/treasury.d.ts.map +1 -1
- package/dist/treasury.js +7 -0
- package/dist/treasury.js.map +1 -1
- package/dist/usaspending.d.ts +32 -1
- package/dist/usaspending.d.ts.map +1 -1
- package/dist/usaspending.js +143 -16
- package/dist/usaspending.js.map +1 -1
- package/package.json +111 -111
- package/src/attachments.ts +652 -652
- package/src/bea.ts +372 -372
- package/src/bls.ts +1943 -1943
- package/src/cache.ts +73 -73
- package/src/cbp-border.ts +177 -0
- package/src/census-economic.ts +431 -431
- package/src/census.ts +735 -735
- package/src/ckan.ts +495 -495
- package/src/clinicaltrials.ts +923 -923
- package/src/cms-facility.ts +379 -379
- package/src/cms-hospital.ts +344 -344
- package/src/cms-supplier.ts +527 -527
- package/src/cms-utilization.ts +389 -389
- package/src/cms.ts +634 -634
- package/src/coerce.ts +47 -47
- package/src/courtlistener.ts +465 -465
- package/src/cpsc.ts +333 -333
- package/src/datagov-catalog.ts +312 -296
- package/src/datagov.ts +907 -907
- package/src/datagovKey.ts +68 -68
- package/src/datasource.ts +721 -721
- package/src/disclosure.ts +61 -61
- package/src/dol.ts +515 -515
- package/src/ecfr.ts +248 -231
- package/src/echo.ts +496 -496
- package/src/edgar.ts +3046 -3014
- package/src/epa-envirofacts.ts +358 -342
- package/src/errors.ts +324 -303
- package/src/fac.ts +529 -529
- package/src/far.ts +1009 -1007
- package/src/fdic.ts +2052 -2052
- package/src/federal-register.ts +725 -706
- package/src/feedback.ts +160 -0
- package/src/fema.ts +680 -541
- package/src/fpds.ts +620 -620
- package/src/fred.ts +464 -464
- package/src/gao.ts +744 -744
- package/src/gov-domains.ts +237 -0
- package/src/govinfo.ts +497 -497
- package/src/grants.ts +290 -290
- package/src/gsa-csv.ts +992 -992
- package/src/gsa-perdiem.ts +361 -361
- package/src/integrity.ts +928 -928
- package/src/keys.ts +268 -268
- package/src/lda.ts +385 -385
- package/src/meta.ts +292 -292
- package/src/nhtsa.ts +352 -352
- package/src/nih.ts +375 -375
- package/src/nist-controls.ts +219 -0
- package/src/nonprofit.ts +460 -460
- package/src/nppes.ts +834 -834
- package/src/nsf.ts +706 -706
- package/src/nvd.ts +1124 -1124
- package/src/nws-weather.ts +167 -0
- package/src/ofac.ts +1166 -1166
- package/src/openfda-device.ts +356 -356
- package/src/openfda-drugsfda.ts +313 -0
- package/src/openfda.ts +518 -495
- package/src/pricing.ts +1075 -1075
- package/src/sam-gov/client.ts +774 -774
- package/src/sam-gov/index.ts +32 -32
- package/src/sam-gov/types.ts +152 -152
- package/src/sba.ts +357 -357
- package/src/server.ts +6688 -6297
- package/src/snapshot.ts +223 -223
- package/src/socrata.ts +532 -532
- package/src/treasury.ts +582 -575
- package/src/usaspending.ts +2852 -2680
- package/src/usitc.ts +420 -420
package/dist/server.js
CHANGED
|
@@ -58,28 +58,34 @@ import * as lda from "./lda.js";
|
|
|
58
58
|
import * as courtlistener from "./courtlistener.js";
|
|
59
59
|
import * as nonprofit from "./nonprofit.js";
|
|
60
60
|
import * as fema from "./fema.js";
|
|
61
|
+
import * as nws from "./nws-weather.js";
|
|
62
|
+
import * as govDomains from "./gov-domains.js";
|
|
61
63
|
import * as fdic from "./fdic.js";
|
|
62
64
|
import * as bls from "./bls.js";
|
|
63
65
|
import * as ofac from "./ofac.js";
|
|
64
66
|
import * as nvd from "./nvd.js";
|
|
67
|
+
import * as nistControls from "./nist-controls.js";
|
|
65
68
|
import * as nppes from "./nppes.js";
|
|
66
69
|
import * as cms from "./cms.js";
|
|
67
70
|
import * as fac from "./fac.js";
|
|
68
71
|
import * as usitc from "./usitc.js";
|
|
69
72
|
import * as openfda from "./openfda.js";
|
|
70
73
|
import * as openfdaDevice from "./openfda-device.js";
|
|
74
|
+
import * as openfdaDrugsfda from "./openfda-drugsfda.js";
|
|
71
75
|
import * as nhtsa from "./nhtsa.js";
|
|
72
76
|
import * as cpsc from "./cpsc.js";
|
|
77
|
+
import * as cbpBorder from "./cbp-border.js";
|
|
73
78
|
import { fetchAttachmentText } from "./attachments.js";
|
|
74
79
|
import * as keys from "./keys.js";
|
|
75
80
|
import { toToolError, ToolErrorCarrier, errorFromResponse } from "./errors.js";
|
|
81
|
+
import * as feedback from "./feedback.js";
|
|
76
82
|
import { buildMeta, isMetaBundle, withMeta, } from "./meta.js";
|
|
77
83
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
78
84
|
import { realpathSync } from "node:fs";
|
|
79
85
|
const SERVER_NAME = "mcp-sam-gov";
|
|
80
86
|
// Kept in lockstep with package.json / manifest.json / server.json.
|
|
81
87
|
// Keep in sync with package.json "version" (asserted at release; see CHANGELOG).
|
|
82
|
-
const SERVER_VERSION = "1.
|
|
88
|
+
const SERVER_VERSION = "1.6.0";
|
|
83
89
|
// ─── Tool input schemas (Zod) ────────────────────────────────────
|
|
84
90
|
const SamSearchInput = z.object({
|
|
85
91
|
query: z.string().optional().describe("Free-text title query"),
|
|
@@ -94,6 +100,7 @@ const SamSearchInput = z.object({
|
|
|
94
100
|
.describe("Place-of-performance state, 2-letter, e.g. 'MD'"),
|
|
95
101
|
setAside: z.array(z.string()).optional().describe("Set-aside codes: SBA, 8A, HZS, SDVOSBC, WOSB, EDWOSB, VSA, VSS"),
|
|
96
102
|
limit: z.number().min(1).max(50).optional(),
|
|
103
|
+
offset: z.number().min(0).optional().describe("Page offset into the result set (default 0)."),
|
|
97
104
|
});
|
|
98
105
|
// Pre-solicitation shaping radar (doc 06 §3.1). Surfaces Sources Sought /
|
|
99
106
|
// Presolicitation / Special Notices BEFORE the RFP exists — the free, real-time
|
|
@@ -178,7 +185,9 @@ const UsasIndividualAwardsInput = UsasFiltersBase.extend({
|
|
|
178
185
|
limit: z.number().min(1).max(50).optional(),
|
|
179
186
|
});
|
|
180
187
|
const UsasSubAgencyInput = z.object({
|
|
181
|
-
agency: z
|
|
188
|
+
agency: z
|
|
189
|
+
.string()
|
|
190
|
+
.describe("Canonical agency NAME (e.g. 'Department of Veterans Affairs'), NOT a toptier code — this filter matches by name; a numeric code silently matches nothing. Resolve via usas_lookup_agency / usas_list_toptier_agencies."),
|
|
182
191
|
fiscalYear: z.number().int().min(2007).optional(),
|
|
183
192
|
});
|
|
184
193
|
const UsasLookupAgencyInput = z.object({
|
|
@@ -192,7 +201,14 @@ const UsasRecipientAwardsInput = z.object({
|
|
|
192
201
|
limit: z.number().min(1).max(50).optional(),
|
|
193
202
|
});
|
|
194
203
|
const UsasSubawardsInput = z.object({
|
|
195
|
-
|
|
204
|
+
// DRIFT/SEMANTICS FIX (dogfooding 2026-07-16): this filters the SUBAWARDEE name,
|
|
205
|
+
// NOT the prime. On spending_by_award{subawards:true} the only keyless recipient
|
|
206
|
+
// filter is `recipient_search_text`, which USAspending matches against the
|
|
207
|
+
// SUB-recipient (live-verified: recipient_search_text:["Leidos"] returns rows
|
|
208
|
+
// whose Sub-Awardee Name IS Leidos, under OTHER primes). The old name
|
|
209
|
+
// `primeRecipientName` promised the opposite. Renamed to `subRecipientName`; the
|
|
210
|
+
// #182 unknown-key guard makes the old name fail loud with the valid-key list.
|
|
211
|
+
subRecipientName: z.string().optional(),
|
|
196
212
|
agency: z.string().optional(),
|
|
197
213
|
naics: z.string().optional(),
|
|
198
214
|
fiscalYear: z.number().int().min(2007).optional(),
|
|
@@ -297,13 +313,19 @@ const UsasSpendingOverTimeInput = z.object({
|
|
|
297
313
|
.optional(),
|
|
298
314
|
});
|
|
299
315
|
const UsasCategorySpendingInput = z.object({
|
|
300
|
-
agency: z
|
|
316
|
+
agency: z
|
|
317
|
+
.string()
|
|
318
|
+
.optional()
|
|
319
|
+
.describe("Canonical agency NAME (e.g. 'Department of Veterans Affairs'), NOT a toptier code — this filter matches by name; a numeric code silently matches nothing. Resolve via usas_lookup_agency."),
|
|
301
320
|
naics: z.string().optional(),
|
|
302
321
|
fiscalYear: z.number().int().min(2007).optional(),
|
|
303
322
|
limit: z.number().min(1).max(50).optional(),
|
|
304
323
|
});
|
|
305
324
|
const UsasCfdaInput = z.object({
|
|
306
|
-
agency: z
|
|
325
|
+
agency: z
|
|
326
|
+
.string()
|
|
327
|
+
.optional()
|
|
328
|
+
.describe("Canonical agency NAME (e.g. 'Department of Veterans Affairs'), NOT a toptier code — this filter matches by name; a numeric code silently matches nothing. Resolve via usas_lookup_agency."),
|
|
307
329
|
fiscalYear: z.number().int().min(2007).optional(),
|
|
308
330
|
limit: z.number().min(1).max(50).optional(),
|
|
309
331
|
});
|
|
@@ -355,6 +377,21 @@ const UsasGlossaryInput = z.object({
|
|
|
355
377
|
const UsasListAgenciesInput = z.object({
|
|
356
378
|
limit: z.number().min(1).max(150).optional(),
|
|
357
379
|
});
|
|
380
|
+
const UsasListDisasterCodesInput = z.object({});
|
|
381
|
+
const UsasDisasterSpendingInput = z.object({
|
|
382
|
+
defCodes: z
|
|
383
|
+
.array(z.string().min(1))
|
|
384
|
+
.min(1)
|
|
385
|
+
.describe("Disaster Emergency Fund Codes (DEFC) to include — REQUIRED. e.g. ['L','M'] (COVID-19 relief) or ['1'] (IIJA / infrastructure). Discover the full code set via usas_list_disaster_codes."),
|
|
386
|
+
spendingType: z
|
|
387
|
+
.enum(["obligation", "outlay"])
|
|
388
|
+
.optional()
|
|
389
|
+
.describe("obligation (default) or outlay. Some DEFCs report $0 obligations but real outlays — try both."),
|
|
390
|
+
geoLayer: z
|
|
391
|
+
.enum(["state", "county", "district"])
|
|
392
|
+
.optional()
|
|
393
|
+
.describe("Geographic breakout: state (default), county, or congressional district."),
|
|
394
|
+
});
|
|
358
395
|
// Federal Register
|
|
359
396
|
const FedRegSearchInput = z.object({
|
|
360
397
|
query: z.string().optional(),
|
|
@@ -797,6 +834,39 @@ const CisaKevLookupInput = z.object({
|
|
|
797
834
|
.describe("Max matches returned (default 100, max 1000)."),
|
|
798
835
|
offset: z.number().min(0).optional().describe("Zero-based page offset (default 0)."),
|
|
799
836
|
});
|
|
837
|
+
const NistControlsInput = z.object({
|
|
838
|
+
controlId: z
|
|
839
|
+
.string()
|
|
840
|
+
.min(1)
|
|
841
|
+
.optional()
|
|
842
|
+
.describe("Exact control identifier, e.g. 'AC-2', 'SC-7', 'AC-2(1)' (case-insensitive; zero-padding is normalized)."),
|
|
843
|
+
family: z
|
|
844
|
+
.string()
|
|
845
|
+
.min(1)
|
|
846
|
+
.optional()
|
|
847
|
+
.describe("Control family — the 2-letter code ('AC', 'SC', 'IA') OR a substring of the family name ('Access Control', 'Audit'). Case-insensitive."),
|
|
848
|
+
keyword: z
|
|
849
|
+
.string()
|
|
850
|
+
.min(1)
|
|
851
|
+
.optional()
|
|
852
|
+
.describe("Case-insensitive substring searched over the control title + requirement statement."),
|
|
853
|
+
limit: z.number().int().min(1).max(200).optional().describe("Max controls returned (default 25, max 200)."),
|
|
854
|
+
offset: z.number().int().min(0).optional().describe("Zero-based page offset (default 0)."),
|
|
855
|
+
});
|
|
856
|
+
const CbpBorderWaitInput = z.object({
|
|
857
|
+
border: z
|
|
858
|
+
.string()
|
|
859
|
+
.min(1)
|
|
860
|
+
.optional()
|
|
861
|
+
.describe("Filter by border — case-insensitive substring, e.g. 'Canadian' or 'Mexican' (the feed labels ports 'Canadian Border' / 'Mexican Border')."),
|
|
862
|
+
portName: z
|
|
863
|
+
.string()
|
|
864
|
+
.min(1)
|
|
865
|
+
.optional()
|
|
866
|
+
.describe("Filter by port name — case-insensitive substring, e.g. 'Laredo', 'Detroit'."),
|
|
867
|
+
limit: z.number().int().min(1).max(200).optional().describe("Max ports returned (default 100, max 200)."),
|
|
868
|
+
offset: z.number().int().min(0).optional().describe("Zero-based page offset (default 0)."),
|
|
869
|
+
});
|
|
800
870
|
// ━━━ NPPES NPI Registry — the healthcare-provider identity/credentialing lane (1) ━━━ ADR-0036
|
|
801
871
|
// nppes_lookup_provider: exact NPI detail OR search over CMS/HHS's keyless public
|
|
802
872
|
// registry of every US healthcare provider (npiregistry.cms.hhs.gov/api, version=2.1
|
|
@@ -1094,8 +1164,10 @@ const TreasuryDatasetEnum = z
|
|
|
1094
1164
|
"mts_table_1",
|
|
1095
1165
|
"rates_of_exchange",
|
|
1096
1166
|
"debt_outstanding",
|
|
1167
|
+
"interest_expense",
|
|
1168
|
+
"tror",
|
|
1097
1169
|
])
|
|
1098
|
-
.describe("Which confirmed Treasury Fiscal Data dataset to query: debt_to_penny (daily total public debt), avg_interest_rates (avg rate by security type), mts_table_1 (Monthly Treasury Statement receipts/outlays/deficit), rates_of_exchange (quarterly FX by currency), debt_outstanding (historical fiscal-year-end debt).");
|
|
1170
|
+
.describe("Which confirmed Treasury Fiscal Data dataset to query: debt_to_penny (daily total public debt), avg_interest_rates (avg rate by security type), mts_table_1 (Monthly Treasury Statement receipts/outlays/deficit), rates_of_exchange (quarterly FX by currency), debt_outstanding (historical fiscal-year-end debt), interest_expense (ACTUAL interest PAID / debt-service cost by security type — distinct from the rate), tror (Treasury Report on Receivables: federal receivables + delinquent-debt collections BY AGENCY).");
|
|
1099
1171
|
const TreasuryQueryDatasetInput = z.object({
|
|
1100
1172
|
dataset: TreasuryDatasetEnum,
|
|
1101
1173
|
fields: z
|
|
@@ -2037,6 +2109,124 @@ const FemaDisasterDeclarationsInput = z.object({
|
|
|
2037
2109
|
.default(0)
|
|
2038
2110
|
.describe("0-based row offset ($skip) for pagination, default 0."),
|
|
2039
2111
|
});
|
|
2112
|
+
const FemaSearchHazardMitigationInput = z.object({
|
|
2113
|
+
state: z
|
|
2114
|
+
.string()
|
|
2115
|
+
.min(1)
|
|
2116
|
+
.optional()
|
|
2117
|
+
.describe("Filter by state (→ state eq '...'). Accepts EITHER a 2-letter code ('AL', like the other FEMA tools) OR the full name ('Alabama'); the module maps a 2-letter code to the full name this dataset requires."),
|
|
2118
|
+
programArea: z
|
|
2119
|
+
.string()
|
|
2120
|
+
.min(1)
|
|
2121
|
+
.optional()
|
|
2122
|
+
.describe("Filter by mitigation program (→ programArea eq '...'): HMGP (Hazard Mitigation Grant Program), FMA (Flood Mitigation Assistance), PDM (Pre-Disaster Mitigation), BRIC (Building Resilient Infrastructure and Communities), LPDM, FMA-SL."),
|
|
2123
|
+
disasterNumber: z
|
|
2124
|
+
.number()
|
|
2125
|
+
.int()
|
|
2126
|
+
.positive()
|
|
2127
|
+
.optional()
|
|
2128
|
+
.describe("Filter by FEMA disaster number (→ disasterNumber eq N)."),
|
|
2129
|
+
status: z
|
|
2130
|
+
.string()
|
|
2131
|
+
.min(1)
|
|
2132
|
+
.optional()
|
|
2133
|
+
.describe("Filter by project status (→ status eq '...'). e.g. 'Closed', 'Open'."),
|
|
2134
|
+
programFy: z
|
|
2135
|
+
.number()
|
|
2136
|
+
.int()
|
|
2137
|
+
.optional()
|
|
2138
|
+
.describe("Filter by program fiscal year (→ programFy eq N). e.g. 2005."),
|
|
2139
|
+
region: z
|
|
2140
|
+
.number()
|
|
2141
|
+
.int()
|
|
2142
|
+
.min(1)
|
|
2143
|
+
.max(10)
|
|
2144
|
+
.optional()
|
|
2145
|
+
.describe("Filter by FEMA region number 1–10 (→ region eq N)."),
|
|
2146
|
+
minProjectAmount: z
|
|
2147
|
+
.number()
|
|
2148
|
+
.optional()
|
|
2149
|
+
.describe("Minimum project amount (→ projectAmount ge N)."),
|
|
2150
|
+
maxProjectAmount: z
|
|
2151
|
+
.number()
|
|
2152
|
+
.optional()
|
|
2153
|
+
.describe("Maximum project amount (→ projectAmount le N)."),
|
|
2154
|
+
limit: z
|
|
2155
|
+
.number()
|
|
2156
|
+
.int()
|
|
2157
|
+
.min(1)
|
|
2158
|
+
.max(1000)
|
|
2159
|
+
.default(100)
|
|
2160
|
+
.describe("Rows per page ($top), 1..1000, default 100."),
|
|
2161
|
+
offset: z
|
|
2162
|
+
.number()
|
|
2163
|
+
.int()
|
|
2164
|
+
.min(0)
|
|
2165
|
+
.default(0)
|
|
2166
|
+
.describe("0-based row offset ($skip) for pagination, default 0."),
|
|
2167
|
+
});
|
|
2168
|
+
const NwsActiveAlertsInput = z.object({
|
|
2169
|
+
state: z
|
|
2170
|
+
.string()
|
|
2171
|
+
.regex(/^[A-Za-z]{2}$/)
|
|
2172
|
+
.optional()
|
|
2173
|
+
.describe("2-letter US state/territory code to scope alerts (→ NWS ?area=), e.g. 'CA'. Omit for all active US alerts."),
|
|
2174
|
+
event: z
|
|
2175
|
+
.string()
|
|
2176
|
+
.min(1)
|
|
2177
|
+
.optional()
|
|
2178
|
+
.describe("Filter by event type — case-insensitive substring, e.g. 'Flood', 'Wind', 'Winter Storm'."),
|
|
2179
|
+
severity: z
|
|
2180
|
+
.enum(["Extreme", "Severe", "Moderate", "Minor", "Unknown"])
|
|
2181
|
+
.optional()
|
|
2182
|
+
.describe("Filter by severity (exact): Extreme | Severe | Moderate | Minor | Unknown."),
|
|
2183
|
+
limit: z.number().int().min(1).max(500).optional().describe("Max alerts returned (default 50, max 500)."),
|
|
2184
|
+
offset: z.number().int().min(0).optional().describe("Zero-based page offset (default 0)."),
|
|
2185
|
+
});
|
|
2186
|
+
const SearchGovDomainsInput = z.object({
|
|
2187
|
+
scope: z
|
|
2188
|
+
.enum(["all", "federal"])
|
|
2189
|
+
.optional()
|
|
2190
|
+
.describe("'all' (federal + SLED: state/county/city/school-district/special-district/tribal, ~16k rows, DEFAULT) or 'federal' (federal-only, ~1.3k rows)."),
|
|
2191
|
+
organization: z
|
|
2192
|
+
.string()
|
|
2193
|
+
.min(1)
|
|
2194
|
+
.optional()
|
|
2195
|
+
.describe("Organization name — case-insensitive SUBSTRING match (e.g. 'veterans', 'cybersecurity')."),
|
|
2196
|
+
domain: z
|
|
2197
|
+
.string()
|
|
2198
|
+
.min(1)
|
|
2199
|
+
.optional()
|
|
2200
|
+
.describe("Domain name — case-insensitive SUBSTRING match (e.g. 'cdc.gov', 'irs')."),
|
|
2201
|
+
domainType: z
|
|
2202
|
+
.string()
|
|
2203
|
+
.min(1)
|
|
2204
|
+
.optional()
|
|
2205
|
+
.describe("Domain type — case-insensitive match (e.g. 'Federal - Executive', 'County', 'Tribal', 'State or territory', 'School district')."),
|
|
2206
|
+
state: z
|
|
2207
|
+
.string()
|
|
2208
|
+
.min(1)
|
|
2209
|
+
.optional()
|
|
2210
|
+
.describe("2-letter state/territory code — case-insensitive exact match (e.g. 'CA')."),
|
|
2211
|
+
city: z
|
|
2212
|
+
.string()
|
|
2213
|
+
.min(1)
|
|
2214
|
+
.optional()
|
|
2215
|
+
.describe("City — case-insensitive SUBSTRING match."),
|
|
2216
|
+
limit: z
|
|
2217
|
+
.number()
|
|
2218
|
+
.int()
|
|
2219
|
+
.min(1)
|
|
2220
|
+
.max(500)
|
|
2221
|
+
.optional()
|
|
2222
|
+
.describe("Rows per page, 1..500, default 50."),
|
|
2223
|
+
offset: z
|
|
2224
|
+
.number()
|
|
2225
|
+
.int()
|
|
2226
|
+
.min(0)
|
|
2227
|
+
.optional()
|
|
2228
|
+
.describe("0-based row offset for pagination, default 0."),
|
|
2229
|
+
});
|
|
2040
2230
|
// ─── EPA ECHO REST (keyless facility compliance/enforcement) — input schemas ──
|
|
2041
2231
|
// ADR-0009. KEYLESS, single fixed host (echodata.epa.gov) + three fixed service
|
|
2042
2232
|
// paths (the SSRF core — no free host/path). `state` is a curated US state/
|
|
@@ -2280,7 +2470,7 @@ const DatagovSearchDatasetsInput = z.object({
|
|
|
2280
2470
|
.min(1)
|
|
2281
2471
|
.max(500)
|
|
2282
2472
|
.optional()
|
|
2283
|
-
.describe("Free-text search over the dataset catalog (→
|
|
2473
|
+
.describe("Free-text search over the dataset catalog (→ q), e.g. 'wildfire'. LIVE-CONFIRMED to narrow (2026-07-16: the v4 API param is `q`; the old `_q` is silently ignored)."),
|
|
2284
2474
|
organization: z
|
|
2285
2475
|
.string()
|
|
2286
2476
|
.min(1)
|
|
@@ -3238,6 +3428,41 @@ const OpenfdaDeviceClearancesInput = z.object({
|
|
|
3238
3428
|
.optional()
|
|
3239
3429
|
.describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
|
|
3240
3430
|
});
|
|
3431
|
+
const OpenfdaDrugApprovalsInput = z.object({
|
|
3432
|
+
sponsorName: z
|
|
3433
|
+
.string()
|
|
3434
|
+
.min(1)
|
|
3435
|
+
.optional()
|
|
3436
|
+
.describe("Sponsor / applicant company name (→ sponsor_name), e.g. 'pfizer'. Matched as an escaped Lucene phrase."),
|
|
3437
|
+
brandName: z
|
|
3438
|
+
.string()
|
|
3439
|
+
.min(1)
|
|
3440
|
+
.optional()
|
|
3441
|
+
.describe("Product brand name (→ products.brand_name), e.g. 'lipitor'. Matched as an escaped Lucene phrase."),
|
|
3442
|
+
activeIngredient: z
|
|
3443
|
+
.string()
|
|
3444
|
+
.min(1)
|
|
3445
|
+
.optional()
|
|
3446
|
+
.describe("Active ingredient name (→ products.active_ingredients.name), e.g. 'atorvastatin calcium'. Matched as an escaped Lucene phrase."),
|
|
3447
|
+
applicationNumber: z
|
|
3448
|
+
.string()
|
|
3449
|
+
.min(1)
|
|
3450
|
+
.optional()
|
|
3451
|
+
.describe("FDA application number (→ application_number), e.g. 'NDA050347'. Matched as an escaped Lucene phrase."),
|
|
3452
|
+
limit: z
|
|
3453
|
+
.number()
|
|
3454
|
+
.int()
|
|
3455
|
+
.min(1)
|
|
3456
|
+
.max(100)
|
|
3457
|
+
.optional()
|
|
3458
|
+
.describe("Max application records to return (default 25, max 100). Offset-paginated via skip."),
|
|
3459
|
+
skip: z
|
|
3460
|
+
.number()
|
|
3461
|
+
.int()
|
|
3462
|
+
.min(0)
|
|
3463
|
+
.optional()
|
|
3464
|
+
.describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
|
|
3465
|
+
});
|
|
3241
3466
|
// ─── NHTSA vehicle safety (api.nhtsa.gov) — KEYLESS vehicle/parts supplier vetting ──
|
|
3242
3467
|
// ADR-0057. Two tools (recalls + complaints) share make/model/modelYear inputs. NO
|
|
3243
3468
|
// API key at all. ★The complaints VIN (PII) is excluded from the output. modelYear is
|
|
@@ -3561,6 +3786,25 @@ const NonprofitFinancialsInput = z.object({
|
|
|
3561
3786
|
});
|
|
3562
3787
|
// api_key_status takes no input — it is a pure status query over process.env.
|
|
3563
3788
|
const ApiKeyStatusInput = z.object({});
|
|
3789
|
+
// `feedback` builds a PREFILLED GitHub issue URL for the human to submit (PULL —
|
|
3790
|
+
// the server never posts). All inputs optional; `summary` is a short, NON-sensitive
|
|
3791
|
+
// title line (the issue is public — the description + return value say so).
|
|
3792
|
+
const FeedbackInput = z.object({
|
|
3793
|
+
kind: z
|
|
3794
|
+
.enum(["bug", "feature", "wrong_output"])
|
|
3795
|
+
.optional()
|
|
3796
|
+
.describe("What kind of report: bug (default), feature (a capability this server lacks), or wrong_output (a tool returned a wrong/suspicious result)."),
|
|
3797
|
+
tool: z
|
|
3798
|
+
.string()
|
|
3799
|
+
.max(80)
|
|
3800
|
+
.optional()
|
|
3801
|
+
.describe("The tool name this is about, if any (e.g. 'sam_search_opportunities')."),
|
|
3802
|
+
summary: z
|
|
3803
|
+
.string()
|
|
3804
|
+
.max(500)
|
|
3805
|
+
.optional()
|
|
3806
|
+
.describe("A short one-line summary for the issue title/body. PUBLIC — never include API keys, personal data, or sensitive query values."),
|
|
3807
|
+
});
|
|
3564
3808
|
// Build a ToolDef whose `handler` is type-checked against the schema's inferred
|
|
3565
3809
|
// input `I` at the call site (e.g. `input.searchText` is known-present). The
|
|
3566
3810
|
// `I` binding is erased to `any` in the ToolDef[] array, so entries without a
|
|
@@ -4097,7 +4341,7 @@ export const TOOLS = [
|
|
|
4097
4341
|
}),
|
|
4098
4342
|
defineTool({
|
|
4099
4343
|
name: "usas_search_subawards",
|
|
4100
|
-
description: "Enumerate subcontracts
|
|
4344
|
+
description: "Enumerate federal subawards (subcontracts), optionally filtered by SUBAWARDEE name. Use for 'where does Leidos appear as a SUBcontractor, and under which primes' — surfaces the prime/sub network for teaming-map artifacts. NOTE: subRecipientName matches the SUB-recipient, NOT the prime (the keyless spending_by_award subaward view has no prime-name filter); to see the subs UNDER a specific prime, resolve that prime's awards first (usas_search_awards → usas_get_award_detail) and read their sub network. Each row carries subRecipient (the subawardee), amount, actionDate, the prime award id, and the prime award's NAICS.",
|
|
4101
4345
|
inputSchema: UsasSubawardsInput,
|
|
4102
4346
|
handler: (input) => usas.searchSubawards(input),
|
|
4103
4347
|
}),
|
|
@@ -4225,6 +4469,18 @@ export const TOOLS = [
|
|
|
4225
4469
|
inputSchema: UsasListAgenciesInput,
|
|
4226
4470
|
handler: (input) => usas.listToptierAgencies(input),
|
|
4227
4471
|
}),
|
|
4472
|
+
defineTool({
|
|
4473
|
+
name: "usas_list_disaster_codes",
|
|
4474
|
+
description: "List the Disaster Emergency Fund Codes (DEFC) — the supplemental-appropriation tags (COVID-19 relief, IIJA/infrastructure, and other emergency laws) that usas_disaster_spending filters on. Keyless USAspending references/def_codes. Returns the COMPLETE code set (no pagination): each `code` with its `group` ('covid_19' | 'infrastructure' | null), `title`, and `publicLaw`. Use this to discover the codes to pass to usas_disaster_spending. HONESTY: group is null (never fabricated) when a code belongs to no named group; totalAvailable is the exact complete count.",
|
|
4475
|
+
inputSchema: UsasListDisasterCodesInput,
|
|
4476
|
+
handler: () => usas.listDisasterCodes(),
|
|
4477
|
+
}),
|
|
4478
|
+
defineTool({
|
|
4479
|
+
name: "usas_disaster_spending",
|
|
4480
|
+
description: "Disaster / emergency-fund spending BY GEOGRAPHY — obligations or outlays tagged to one or more Disaster Emergency Fund Codes (DEFC: COVID-19, IIJA, etc.), broken out per state / county / congressional district (keyless USAspending disaster/spending_by_geography). Answers 'which geographies captured COVID/IIJA relief money' — a distinct axis the standard award search does not expose. `defCodes` REQUIRED (discover via usas_list_disaster_codes); `spendingType` obligation (default) | outlay; `geoLayer` state (default) | county | district. Each row: name, code, amount, awardCount, population, perCapita. HONESTY: amount/perCapita are number|null (a real 0 stays 0 — some DEFCs like IIJA report $0 OBLIGATIONS with a nonzero awardCount, disclosed in a note; absent → null, never a fabricated 0); the endpoint returns the COMPLETE set of geo units (no pagination) so totalAvailable = returned; an outage/4xx THROWS (never a fake empty).",
|
|
4481
|
+
inputSchema: UsasDisasterSpendingInput,
|
|
4482
|
+
handler: (input) => usas.disasterSpending(input),
|
|
4483
|
+
}),
|
|
4228
4484
|
// ━━━ Federal Register (4) ━━━
|
|
4229
4485
|
defineTool({
|
|
4230
4486
|
name: "fed_register_search_documents",
|
|
@@ -4365,6 +4621,12 @@ export const TOOLS = [
|
|
|
4365
4621
|
inputSchema: CisaKevLookupInput,
|
|
4366
4622
|
handler: (input) => nvd.cisaKevLookup(input),
|
|
4367
4623
|
}),
|
|
4624
|
+
defineTool({
|
|
4625
|
+
name: "nist_800_53_controls",
|
|
4626
|
+
description: "Look up NIST SP 800-53 Rev 5 security & privacy CONTROLS (keyless) — the requirement backbone for FedRAMP / CMMC / RMF compliance work. Retrieve a control by `controlId` (exact, e.g. 'AC-2', 'SC-7', 'AC-2(1)'), a `family` (2-letter code 'AC'/'SC'/'IA' or a name substring 'Access Control'), and/or a `keyword` (case-insensitive substring over title + statement); `limit`/`offset` pagination. Each row: { id (e.g. 'AC-2'), family (e.g. 'AC — Access Control'), title, statement (the labelled requirement prose), guidance (discussion), enhancements:[{id,title}] (e.g. AC-2(1)) }. Complements cve_lookup + cisa_kev_lookup (the vulnerability side) with the CONTROL/requirement side. HONESTY: source is NIST's OFFICIAL OSCAL catalog published at github.com/usnistgov/oscal-content (authoritative first-party data served from GitHub, not a .gov API host — provenance disclosed in _meta); the catalog has no query API so filtering is CLIENT-SIDE and totalAvailable is the EXACT match count; this is the REQUIREMENT text only — applicability depends on the system's FIPS-199 impact baseline (Low/Moderate/High), which the catalog does not encode (disclosed); a download failure or an implausibly-truncated catalog (< 15 families) THROWS (never a fake-empty 'control not found').",
|
|
4627
|
+
inputSchema: NistControlsInput,
|
|
4628
|
+
handler: (input) => nistControls.searchControls(input),
|
|
4629
|
+
}),
|
|
4368
4630
|
// ━━━ NPPES NPI Registry — Healthcare-Provider Vetting (1) ━━━ ADR-0036
|
|
4369
4631
|
defineTool({
|
|
4370
4632
|
name: "nppes_lookup_provider",
|
|
@@ -4408,7 +4670,7 @@ export const TOOLS = [
|
|
|
4408
4670
|
// ━━━ US Treasury — Fiscal Data (keyless) (4) ━━━ ADR-0002
|
|
4409
4671
|
defineTool({
|
|
4410
4672
|
name: "treasury_query_dataset",
|
|
4411
|
-
description: "Escape-hatch query over
|
|
4673
|
+
description: "Escape-hatch query over 7 confirmed US Treasury Fiscal Data datasets (keyless): debt_to_penny, avg_interest_rates, mts_table_1 (Monthly Treasury Statement), rates_of_exchange, debt_outstanding, interest_expense (actual interest PAID / debt-service cost), tror (Treasury Report on Receivables — federal receivables + delinquent-debt collections by agency). Choose `dataset` (enum — no free path), and optionally project `fields` (CSV), `filter` (CSV 'col:op:val', ops lt|lte|gt|gte|eq|in, AND-combined), and `sort` (CSV, '-' = desc), with page[size]/page[number] pagination. Returns raw rows plus a truthful `_meta` (totalAvailable = upstream total-count, offset pagination). Value/amount fields are raw upstream strings — the string \"null\"/empty means 'no value', never 0. Covers rates_of_exchange + debt_outstanding without a dedicated tool.",
|
|
4412
4674
|
inputSchema: TreasuryQueryDatasetInput,
|
|
4413
4675
|
handler: (input) => treasury.queryDataset(input),
|
|
4414
4676
|
}),
|
|
@@ -4601,7 +4863,7 @@ export const TOOLS = [
|
|
|
4601
4863
|
inputSchema: BlsQcewInput,
|
|
4602
4864
|
handler: (input) => bls.qcew(input),
|
|
4603
4865
|
}),
|
|
4604
|
-
// ━━━ OpenFEMA — keyless disaster declarations + emergency-assistance spend (
|
|
4866
|
+
// ━━━ OpenFEMA — keyless disaster declarations + emergency-assistance spend (3) ━━━ ADR-0016
|
|
4605
4867
|
defineTool({
|
|
4606
4868
|
name: "fema_search_public_assistance",
|
|
4607
4869
|
description: "Search FEMA Public Assistance funded projects — federal emergency-assistance spend to state/local/tribal applicants (keyless OpenFEMA, dataset PublicAssistanceFundedProjectsDetails v2, ~800k rows). Structured filters (module-built into an OData $filter; each LIVE-VERIFIED to narrow): `state` (→ stateAbbreviation), `disasterNumber`, `applicantId`, `damageCategoryCode` (e.g. 'B' = Emergency Protective Measures), `incidentType`, `minProjectAmount`/`maxProjectAmount` (projectAmount ge/le), `declaredDateFrom`/`declaredDateTo` (declarationDate ge/le). `limit` (≤1000, def 100 → $top), `offset` (→ $skip). HONESTY: the module ALWAYS sends $inlinecount=allpages so totalAvailable is the EXACT filtered total (metadata.count), never the page length; amount fields are number|null (a real 0 stays 0, absent → null); genuine-empty ⇒ complete:true/total:0; an outage/400/404 THROWS (never a fake empty).",
|
|
@@ -4614,6 +4876,26 @@ export const TOOLS = [
|
|
|
4614
4876
|
inputSchema: FemaDisasterDeclarationsInput,
|
|
4615
4877
|
handler: (input) => fema.disasterDeclarations(input),
|
|
4616
4878
|
}),
|
|
4879
|
+
defineTool({
|
|
4880
|
+
name: "fema_search_hazard_mitigation",
|
|
4881
|
+
description: "Search FEMA Hazard Mitigation Assistance projects — the disaster-RESILIENCE grant axis (HMGP/FMA/PDM/BRIC mitigation grants to state/local/tribal subrecipients, distinct from the disaster-RECOVERY spend in fema_search_public_assistance). Keyless OpenFEMA, dataset HazardMitigationAssistanceProjects v4, ~56k rows. Structured filters (module-built into an OData $filter; each LIVE-VERIFIED to narrow): `state` (→ state — the FULL state NAME, e.g. 'Alabama', NOT the 2-letter code), `programArea` (HMGP/FMA/PDM/BRIC/LPDM/FMA-SL), `disasterNumber`, `status` (e.g. 'Closed'), `programFy`, `region` (FEMA region 1–10), `minProjectAmount`/`maxProjectAmount` (projectAmount ge/le). `limit` (≤1000, def 100 → $top), `offset` (→ $skip). HONESTY: the module ALWAYS sends $inlinecount=allpages so totalAvailable is the EXACT filtered total (metadata.count), never the page length; amount fields (projectAmount/federalShareObligated/initialObligationAmount/netValueBenefits) are number|null (a real 0 stays 0, absent → null); genuine-empty ⇒ complete:true/total:0; an outage/400/404 THROWS (never a fake empty). NOTE: 'state' here is the full name (this dataset 400s on a 2-letter code), whereas fema_search_public_assistance maps 'state' to the 2-letter 'stateAbbreviation'.",
|
|
4882
|
+
inputSchema: FemaSearchHazardMitigationInput,
|
|
4883
|
+
handler: (input) => fema.searchHazardMitigation(input),
|
|
4884
|
+
}),
|
|
4885
|
+
// ━━━ NWS — National Weather Service active alerts (keyless) (1) ━━━
|
|
4886
|
+
defineTool({
|
|
4887
|
+
name: "nws_active_alerts",
|
|
4888
|
+
description: "List CURRENTLY-ACTIVE National Weather Service alerts — watches, warnings, and advisories (keyless; api.weather.gov). The disaster/climate-readiness lane that pairs with the FEMA tools (declarations → public assistance → hazard mitigation → LIVE active weather): where severe-weather events are active NOW, ahead of the declarations/contracts that follow. Filters: `state` (2-letter code → server-side ?area=, e.g. 'CA'; omit for all US), `event` (case-insensitive substring, e.g. 'Flood', 'Wind'), `severity` (Extreme/Severe/Moderate/Minor/Unknown); `limit`/`offset` pagination. Each alert: { id, event, headline, severity, urgency, certainty, category, status, messageType, areaDesc, effective, onset, expires, ends, senderName, description, instruction, response }. HONESTY: this is REAL-TIME data (alerts active at request time — a live snapshot, NOT a historical archive; read effective/expires for each window, disclosed in _meta); every scalar is null-never-empty-string and dates are ISO strings; totalAvailable is the EXACT count of matched active alerts; a NO-active-alerts result is an HONEST EMPTY (returned:0), never an error; an outage/4xx/timeout THROWS and a non-FeatureCollection body ⇒ schema_drift. A descriptive User-Agent is sent per NWS policy (no key/token).",
|
|
4889
|
+
inputSchema: NwsActiveAlertsInput,
|
|
4890
|
+
handler: (input) => nws.activeAlerts(input),
|
|
4891
|
+
}),
|
|
4892
|
+
// ━━━ get.gov — CISA authoritative .gov domain registry (keyless) (1) ━━━
|
|
4893
|
+
defineTool({
|
|
4894
|
+
name: "search_gov_domains",
|
|
4895
|
+
description: "Search the authoritative US .gov domain registry (CISA get.gov) — resolve which ORGANIZATION owns a .gov domain, enumerate federal agencies, and MAP SLED entities (state/county/city/school-district/special-district/tribal) for market targeting. Keyless. scope 'all' (federal + SLED, ~16k rows, default) | 'federal'. Filters (client-side over the published CSV): organization/domain/city (case-insensitive SUBSTRING), domainType (e.g. 'Federal - Executive', 'County', 'Tribal'), state (2-letter). Each row: domain, domainType, organization, suborganization, city, state. HONESTY: source is CISA's OFFICIAL registry published at github.com/cisagov/dotgov-data (authoritative first-party data, not a .gov API host — provenance disclosed in _meta); the registry has no query API so filtering is CLIENT-SIDE and totalAvailable is the EXACT match count; the 'Security contact email' column is intentionally EXCLUDED (org mailbox — this tool resolves organizations, not contacts); an outage/4xx THROWS (never a fake empty); a header-column rename ⇒ schema_drift.",
|
|
4896
|
+
inputSchema: SearchGovDomainsInput,
|
|
4897
|
+
handler: (input) => govDomains.searchGovDomains(input),
|
|
4898
|
+
}),
|
|
4617
4899
|
// ━━━ FPDS-NG — federal contract AWARD ACTIONS (keyless ATOM) (1) ━━━ ADR-0012
|
|
4618
4900
|
// The FIRST XML/ATOM source (bounded, ReDoS-safe hand-parser — the far.ts/gao.ts
|
|
4619
4901
|
// lineage; NOT the getJson port). FPDS is the system-of-record USAspending
|
|
@@ -4952,6 +5234,12 @@ export const TOOLS = [
|
|
|
4952
5234
|
inputSchema: OpenfdaDeviceClearancesInput,
|
|
4953
5235
|
handler: (input) => openfdaDevice.deviceClearances(input),
|
|
4954
5236
|
}),
|
|
5237
|
+
defineTool({
|
|
5238
|
+
name: "openfda_drug_approvals",
|
|
5239
|
+
description: "Search openFDA Drugs@FDA DRUG APPROVALS — FDA-approved drug applications (NDA/ANDA/BLA) with the sponsor, application number, each approved product (brand + generic/active-ingredient name, dosage form, route, marketing status), and the submission/approval history (openFDA /drug/drugsfda.json; api.fda.gov). Answers 'what drugs did sponsor X get approved, and which are still marketed' — pharma vendor product/approval intelligence. KEYLESS (an OPTIONAL free OPENFDA_API_KEY only RAISES the rate limit — keyless works at ~1000 requests/day; NEVER throws for a missing key; api_key_status lists every source's key requirement). Input: STRUCTURED filters — `sponsorName` (→sponsor_name), `brandName` (→products.brand_name), `activeIngredient` (→products.active_ingredients.name), `applicationNumber` (→application_number) — safely escaped into the openFDA search= Lucene string (NO raw passthrough — injection-safe), plus `limit` (1..100, default 25) and `skip` (offset ≥0). Returns { applications:[{ applicationNumber, sponsorName, products:[{ brandName, genericIngredients:[{name,strength}], dosageForm, route, marketingStatus }], submissions:[{ submissionType, submissionNumber, submissionStatus, submissionStatusDate, submissionClass }] }] } + honest _meta. HONESTY: totalAvailable is openFDA's EXACT meta.results.total (skip/limit pagination — never results.length); every scalar is null-never-empty-string; a 'Discontinued' marketingStatus is NOT an approval revocation (disclosed in _meta). ★A no-match query returns openFDA HTTP 404 NOT_FOUND ⇒ an HONEST EMPTY (returned:0/total:0), NOT an error; a 400 ⇒ invalid_input surfacing openFDA's message; a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The optional key rides ONLY the &api_key= query param — never logged or echoed.",
|
|
5240
|
+
inputSchema: OpenfdaDrugApprovalsInput,
|
|
5241
|
+
handler: (input) => openfdaDrugsfda.drugApprovals(input),
|
|
5242
|
+
}),
|
|
4955
5243
|
// ━━━ NHTSA vehicle safety (api.nhtsa.gov) — vehicle/parts supplier vetting (2) ━━━ ADR-0057
|
|
4956
5244
|
// ★KEYLESS — no API key at all (no parameter, no header). The cross-agency
|
|
4957
5245
|
// product-safety family alongside openFDA (medical). Both tools share
|
|
@@ -4983,6 +5271,13 @@ export const TOOLS = [
|
|
|
4983
5271
|
inputSchema: CpscRecallsInput,
|
|
4984
5272
|
handler: (input) => cpsc.recalls(input),
|
|
4985
5273
|
}),
|
|
5274
|
+
// ━━━ CBP Border Wait Times (bwt.cbp.gov) — freight/logistics (1) ━━━
|
|
5275
|
+
defineTool({
|
|
5276
|
+
name: "cbp_border_wait_times",
|
|
5277
|
+
description: "Live CBP land-border-port wait times — current commercial-vehicle (and passenger) crossing delays at every US Canadian- and Mexican-border port (keyless; bwt.cbp.gov). The FREIGHT / LOGISTICS situational-awareness lane: per-port commercial-vehicle standard + FAST lane delay (minutes), operational status, open-lane count, and maximum lanes. Filters (optional): `border` (case-insensitive substring, 'Canadian'/'Mexican'), `portName` (substring, e.g. 'Laredo'); `limit`/`offset` pagination. Each row: { portNumber, portName, crossingName, border, portStatus (Open/Closed), asOf, commercialVehicle:{ maxLanes, standard:{operationalStatus, delayMinutes, lanesOpen, updateTime}, fast:{…} } }. HONESTY: this is REAL-TIME operational data — each lane carries its own updateTime (surfaced verbatim; freshness never implied live-to-the-second); delayMinutes/lanesOpen are number|null (a real 0 stays 0; an empty/N/A value — e.g. a closed lane — is null, NEVER a fabricated 0, because a closed lane's delay is UNKNOWN, not zero); the API returns the WHOLE port set so totalAvailable is the EXACT matched-port count; an outage/4xx/timeout THROWS and a non-array body ⇒ schema_drift (never a fake empty).",
|
|
5278
|
+
inputSchema: CbpBorderWaitInput,
|
|
5279
|
+
handler: (input) => cbpBorder.borderWaitTimes(input),
|
|
5280
|
+
}),
|
|
4986
5281
|
// ━━━ BEA Regional Economic Accounts (apps.bea.gov) — regional GDP/income (1) ━━━ ADR-0051
|
|
4987
5282
|
// ★The server's THIRD KEY-REQUIRED source: the BEA Data API has NO keyless tier, so
|
|
4988
5283
|
// WITHOUT a BEA_API_KEY this tool throws an honest invalid_input config error (the
|
|
@@ -5087,6 +5382,17 @@ export const TOOLS = [
|
|
|
5087
5382
|
inputSchema: ApiKeyStatusInput,
|
|
5088
5383
|
handler: async () => keys.apiKeyStatus(),
|
|
5089
5384
|
}),
|
|
5385
|
+
// ━━━ Meta: in-product feedback → GitHub issue (PULL-only, keyless) ━━━
|
|
5386
|
+
// The server's "user" is an AI agent, so we collect real-usage friction THROUGH
|
|
5387
|
+
// it: this returns a PREFILLED GitHub new-issue URL for the HUMAN to open and
|
|
5388
|
+
// submit. The server NEVER posts (no token/account/auto-submit) and embeds no
|
|
5389
|
+
// PII — only the caller's summary + tool name. See feedback.ts.
|
|
5390
|
+
defineTool({
|
|
5391
|
+
name: "feedback",
|
|
5392
|
+
description: "Report a problem, request a feature, or flag a wrong-looking result for THIS server — returns a PREFILLED GitHub issue link (public repo cliwant/mcp-sam-gov) for the USER to open and submit. KEYLESS. The server does NOT post anything: it only builds the link; the human submits it (nothing is sent automatically, no network call). Use when the user reports a bug, says a tool's output looks wrong, or wants a capability this server doesn't have. Input (all optional): `kind` (bug|feature|wrong_output, default bug), `tool` (the tool it's about), `summary` (a short, NON-SENSITIVE title line). Returns { reportUrl, repo, willPost:false, instructions, privacy }. PRIVACY: never put secrets, personal data, or sensitive query values in `summary` — the issue is PUBLIC and the link prefills only your summary + tool + server version.",
|
|
5393
|
+
inputSchema: FeedbackInput,
|
|
5394
|
+
handler: async (input) => feedback.feedbackTool(input),
|
|
5395
|
+
}),
|
|
5090
5396
|
];
|
|
5091
5397
|
// ─── Server bootstrap ────────────────────────────────────────────
|
|
5092
5398
|
async function main() {
|
|
@@ -5107,7 +5413,12 @@ async function main() {
|
|
|
5107
5413
|
},
|
|
5108
5414
|
},
|
|
5109
5415
|
});
|
|
5110
|
-
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
5416
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, {
|
|
5417
|
+
capabilities: { tools: {} },
|
|
5418
|
+
// Surfaced to the agent at initialize. Tells it how to route real-usage
|
|
5419
|
+
// friction back to the project WITHOUT the server ever posting anything.
|
|
5420
|
+
instructions: "This server wraps US government open data (keyless-first). If a tool result looks wrong, a tool stays broken, or the user wants a capability this server lacks, help improve it: call the `feedback` tool — or use the `report` URL present on schema_drift / upstream_unavailable errors — to get a PREFILLED GitHub issue link, and offer it to the user to open and submit. Nothing is posted automatically; the user submits. Never include secrets or personal data in a report (the repo is public).",
|
|
5421
|
+
});
|
|
5111
5422
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
5112
5423
|
return {
|
|
5113
5424
|
tools: TOOLS.map((t) => ({
|
|
@@ -5146,6 +5457,12 @@ async function main() {
|
|
|
5146
5457
|
// NON-retryable `invalid_input` with readable field-level issues — never a
|
|
5147
5458
|
// generic `unknown` carrying Zod's raw JSON dump.
|
|
5148
5459
|
const error = toToolError(err, name);
|
|
5460
|
+
// PULL-only feedback loop (feedback.ts): attach a PREFILLED GitHub issue URL
|
|
5461
|
+
// ONLY for the two "something may be broken" kinds — schema_drift (the
|
|
5462
|
+
// upstream changed shape) and upstream_unavailable (a possible moved/broken
|
|
5463
|
+
// endpoint). Carries only tool + kind + version (no args/PII). The 429 and
|
|
5464
|
+
// invalid_input/not_found envelopes stay byte-identical (no `report`).
|
|
5465
|
+
feedback.maybeAttachReport(error, name, SERVER_VERSION);
|
|
5149
5466
|
const envelope = { ok: false, error };
|
|
5150
5467
|
return {
|
|
5151
5468
|
content: [
|
|
@@ -5221,6 +5538,32 @@ function synthesizeDefaultMeta(toolName, sam) {
|
|
|
5221
5538
|
}
|
|
5222
5539
|
return buildMeta({ source, keylessMode, complete: true, truncated: false });
|
|
5223
5540
|
}
|
|
5541
|
+
// Unwrap a tool's inputSchema down to its underlying ZodObject so we can read the
|
|
5542
|
+
// set of declared top-level keys. Tools wrap the object in .refine()/.superRefine()
|
|
5543
|
+
// (ZodEffects), or occasionally .optional()/.default()/.nullable(), so peel those
|
|
5544
|
+
// layers. Returns null if no ZodObject is reachable (then unknown-key rejection is
|
|
5545
|
+
// skipped for that tool — fail open, never fail closed on our own introspection).
|
|
5546
|
+
function objectSchemaOf(schema) {
|
|
5547
|
+
let s = schema;
|
|
5548
|
+
for (let i = 0; i < 20; i++) {
|
|
5549
|
+
const def = s?._def;
|
|
5550
|
+
if (!def)
|
|
5551
|
+
break;
|
|
5552
|
+
const tn = def.typeName;
|
|
5553
|
+
if (tn === "ZodObject")
|
|
5554
|
+
return s;
|
|
5555
|
+
if (tn === "ZodEffects") {
|
|
5556
|
+
s = def.schema;
|
|
5557
|
+
continue;
|
|
5558
|
+
}
|
|
5559
|
+
if (tn === "ZodOptional" || tn === "ZodDefault" || tn === "ZodNullable") {
|
|
5560
|
+
s = def.innerType;
|
|
5561
|
+
continue;
|
|
5562
|
+
}
|
|
5563
|
+
break;
|
|
5564
|
+
}
|
|
5565
|
+
return null;
|
|
5566
|
+
}
|
|
5224
5567
|
export async function runTool(name, args, sam) {
|
|
5225
5568
|
// R1 (ADR-0001) — registry dispatch. Every tool's TOOLS[] entry carries a
|
|
5226
5569
|
// co-located `handler`: route through it by parsing `args` with the entry's
|
|
@@ -5230,6 +5573,26 @@ export async function runTool(name, args, sam) {
|
|
|
5230
5573
|
// gone (all 52 tools migrated) — an unknown name has no entry and throws.
|
|
5231
5574
|
const entry = TOOLS.find((t) => t.name === name);
|
|
5232
5575
|
if (entry?.handler) {
|
|
5576
|
+
// HONESTY (dogfooding 2026-07-15): reject UNKNOWN top-level input keys LOUD
|
|
5577
|
+
// instead of Zod's default silent-strip. A misspelled filter (naicsCode↔naics,
|
|
5578
|
+
// keyword↔query) was otherwise dropped and the tool scanned the WHOLE corpus,
|
|
5579
|
+
// returning an authoritative-looking WRONG answer with no error. We name the
|
|
5580
|
+
// unknown key(s) and list the valid ones so the mistake self-corrects. Skip
|
|
5581
|
+
// when the schema intentionally accepts extras (unknownKeys==="passthrough").
|
|
5582
|
+
const obj = objectSchemaOf(entry.inputSchema);
|
|
5583
|
+
if (obj && obj._def.unknownKeys !== "passthrough" && args && typeof args === "object") {
|
|
5584
|
+
const known = new Set(Object.keys(obj.shape));
|
|
5585
|
+
const unknown = Object.keys(args).filter((k) => !known.has(k));
|
|
5586
|
+
if (unknown.length > 0) {
|
|
5587
|
+
throw new ToolErrorCarrier({
|
|
5588
|
+
kind: "invalid_input",
|
|
5589
|
+
message: `Unknown input ${unknown.length > 1 ? "keys" : "key"} for ${name}: ` +
|
|
5590
|
+
`${unknown.map((k) => `'${k}'`).join(", ")}. ` +
|
|
5591
|
+
`Valid keys: ${[...known].sort().join(", ")}.`,
|
|
5592
|
+
retryable: false,
|
|
5593
|
+
});
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5233
5596
|
const input = entry.inputSchema.parse(args);
|
|
5234
5597
|
return await entry.handler(input, { sam });
|
|
5235
5598
|
}
|