@cliwant/mcp-sam-gov 1.9.0 → 1.10.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/dist/arcgis-feature.d.ts +68 -0
- package/dist/arcgis-feature.d.ts.map +1 -0
- package/dist/arcgis-feature.js +206 -0
- package/dist/arcgis-feature.js.map +1 -0
- package/dist/arcgis-hub.d.ts +90 -0
- package/dist/arcgis-hub.d.ts.map +1 -0
- package/dist/arcgis-hub.js +210 -0
- package/dist/arcgis-hub.js.map +1 -0
- package/dist/bonfire.d.ts +69 -0
- package/dist/bonfire.d.ts.map +1 -0
- package/dist/bonfire.js +208 -0
- package/dist/bonfire.js.map +1 -0
- package/dist/opengov.d.ts +96 -0
- package/dist/opengov.d.ts.map +1 -0
- package/dist/opengov.js +244 -0
- package/dist/opengov.js.map +1 -0
- package/dist/pricing.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +166 -1
- package/dist/server.js.map +1 -1
- package/dist/socrata.d.ts +27 -16
- package/dist/socrata.d.ts.map +1 -1
- package/dist/socrata.js +119 -18
- package/dist/socrata.js.map +1 -1
- package/package.json +1 -1
- package/src/arcgis-feature.ts +232 -0
- package/src/arcgis-hub.ts +270 -0
- package/src/bonfire.ts +245 -0
- package/src/opengov.ts +309 -0
- package/src/server.ts +178 -1
- package/src/socrata.ts +123 -18
package/src/socrata.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Socrata / SODA — keyless open data for state / local (SLED) + E-rate portals.
|
|
2
|
+
* Socrata / SODA — keyless open data for state / local (SLED) + federal + E-rate portals.
|
|
3
3
|
*
|
|
4
4
|
* First SLED source (ADR-0004); 3rd consumer of the fetch/map/meta shape after
|
|
5
5
|
* treasury.ts / edgar.ts. Fully PUBLIC, KEYLESS. ONE connector reaches ~a dozen
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* 4x4 dataset id. Hosts are a CURATED allowlist (see SSRF below); the caller
|
|
8
8
|
* never supplies a free host or a free path.
|
|
9
9
|
* Row query: https://{domain}/resource/{4x4}.json?$select=…&$where=…&$limit=…
|
|
10
|
-
* Catalog:
|
|
10
|
+
* Catalog (host-scoped): https://{domain}/api/catalog/v1?search_context={domain}&q=…
|
|
11
|
+
* Catalog (all-host): https://api.us.socrata.com/api/catalog/v1?domains=…&q=…
|
|
11
12
|
*
|
|
12
13
|
* Three layers (mirror treasury.ts / edgar.ts):
|
|
13
14
|
* fetch — `getSocrataResource` / `getCatalog`: SSRF-guard, build the URL,
|
|
@@ -67,14 +68,19 @@
|
|
|
67
68
|
* wrong shape (possible upstream API change). A hard schema_drift throw is
|
|
68
69
|
* reserved for a PRIMARY query (rows / catalog), NEVER the secondary count.
|
|
69
70
|
*
|
|
70
|
-
* ALLOWLIST —
|
|
71
|
-
*
|
|
71
|
+
* ALLOWLIST — each entry carries a real sample 4x4, live-verified on its tier's
|
|
72
|
+
* date (base state slice 2026-07-10; local + major-city + federal 2026-07-18).
|
|
73
|
+
* All `.gov` except the documented non-.gov exceptions: `opendata.usac.org` and
|
|
74
|
+
* the four major-city portals (.us/.org — see the inline blocks below):
|
|
72
75
|
* m6 — `opendata.usac.org` is a `.org` (USAC, a Congress-designated non-profit;
|
|
73
76
|
* E-rate). It is on the periodic re-verification checklist. NOTE: the
|
|
74
|
-
*
|
|
75
|
-
* (
|
|
76
|
-
* surface it
|
|
77
|
-
* (
|
|
77
|
+
* FEDERATED discovery catalog (api.us.socrata.com) does NOT index USAC
|
|
78
|
+
* (resultSetSize 0), so the ALL-HOST `socrata_discover_datasets` (domain
|
|
79
|
+
* omitted) won't surface it; as of loop cycle 8 a DOMAIN-scoped discover
|
|
80
|
+
* uses USAC's own catalog (search_context) and DOES surface it (live:
|
|
81
|
+
* opendata.usac.org/api/catalog/v1?search_context=… → resultSetSize 8).
|
|
82
|
+
* `socrata_query` works regardless with a known 4x4 (live:
|
|
83
|
+
* opendata.usac.org/resource/avi8-svp9.json → 200 bare array).
|
|
78
84
|
* M1 — MA is DROPPED from slice 1: `cthru.data.socrata.com` is a commercial
|
|
79
85
|
* vendor host (Tyler Technologies `.socrata.com`, not gov-controlled) and
|
|
80
86
|
* no `.gov` MA Socrata host verifies (`data.mass.gov` → the catalog
|
|
@@ -139,7 +145,55 @@ export const SOCRATA_DOMAINS = [
|
|
|
139
145
|
"data.montgomerycountymd.gov", // Montgomery County MD — e.g. vmu2-pnrc (Contracts)
|
|
140
146
|
"data.mesaaz.gov", // Mesa AZ (city) — e.g. j7s9-qiuq (Vendor Payments)
|
|
141
147
|
"data.cambridgema.gov", // Cambridge MA (city) — e.g. gp98-ja4f (Contracts bid list)
|
|
148
|
+
// ── Federal open-data portal tier (loop cycle 7, 2026-07-18). The FIRST
|
|
149
|
+
// federal Socrata hosts (prior tiers were state + local only); all .gov,
|
|
150
|
+
// host-scoped catalog + /resource/<4x4>.json 200 bare-array + count(*)
|
|
151
|
+
// companion live-verified. High-value B2G federal datasets (carrier/company
|
|
152
|
+
// census, transportation infrastructure & stats, public health).
|
|
153
|
+
// NOTE (m3/under-index): the FEDERATED aggregator (api.us.socrata.com)
|
|
154
|
+
// under-indexes these hosts (DOT: 3 federated vs 1,873 host-scoped). As of
|
|
155
|
+
// loop cycle 8, `socrata_discover_datasets` WITH a domain uses each host's
|
|
156
|
+
// OWN catalog (search_context) → complete per-host discovery; only the
|
|
157
|
+
// all-host search (domain omitted) still relies on the federated aggregator
|
|
158
|
+
// (its `_meta` note discloses the gap). ──────────────────────────────────
|
|
159
|
+
"data.transportation.gov", // US DOT — e.g. az4n-8mr2 (Company Census File, ~4.47M carriers)
|
|
160
|
+
"data.cdc.gov", // US CDC — e.g. 9bhg-hcku (Provisional COVID-19 Deaths)
|
|
161
|
+
"data.bts.gov", // US BTS (DOT) — e.g. keg4-3bc2 (Border Crossing Entry Data)
|
|
162
|
+
// ── SLED procurement bid-catalog tier (loop cycle 11, 2026-07-19; from the
|
|
163
|
+
// exhaustive US state+local bid-site research). All .gov; host-scoped catalog
|
|
164
|
+
// + /resource/<4x4>.json 200 bare-array live-verified. Distinctive value:
|
|
165
|
+
// these carry LIVE bid-cycle data (bid tabulations / anticipated
|
|
166
|
+
// solicitations), not only award/spend. (★4 other candidate hosts —
|
|
167
|
+
// data.iowa.gov, data.scottsdaleaz.gov, data.gilbertaz.gov, opendata.hawaii.gov
|
|
168
|
+
// — were REJECTED: their /api/catalog/v1 AND /resource endpoints 404 (Next.js/
|
|
169
|
+
// Express apps, not Socrata — a research-agent "Socrata" claim that live
|
|
170
|
+
// verification disproved). Never add a host without a 200 bare-array probe.) ─
|
|
171
|
+
"datacatalog.cookcountyil.gov", // Cook County IL — e.g. 32au-zaqn (Bid Tabulations ~5,607; +awards qh8j-6k63, intent-to-award bgq7-v7ms — 17 procurement datasets)
|
|
172
|
+
"data.illinois.gov", // IL — e.g. 6rb8-ntpm (Future Solicitations = anticipated construction bids); re-included (cycle-1 excluded on federated-discover 0, but host-scoped catalog + /resource verified 2026-07-19)
|
|
173
|
+
"data.cincinnati-oh.gov", // Cincinnati OH (city) — e.g. 2iq3-bugw (Certified Vendors MBE/WBE; +contracts 85xi-xdtw)
|
|
174
|
+
// ── Major-city OFFICIAL portals (loop cycle 5, 2026-07-18). NON-.gov but the
|
|
175
|
+
// unambiguously-official municipal open-data portals for the largest local
|
|
176
|
+
// procurement markets (NYC OpenData / Chicago / DataSF / LA Controller) —
|
|
177
|
+
// host-scoped catalog + /resource 200 bare-array verified. Trust-boundary
|
|
178
|
+
// (like the USAC .org exception): the SSRF guard is the CURATED, FROZEN
|
|
179
|
+
// allowlist, not the TLD; these five are the documented non-.gov entries. ──
|
|
180
|
+
"data.cityofnewyork.us", // NYC OpenData (.us, official) — e.g. dg92-zbpx (City Record: procurement notices)
|
|
181
|
+
"data.cityofchicago.org", // Chicago (.org, official) — e.g. rsxa-ify5 (Contracts)
|
|
182
|
+
"data.sfgov.org", // San Francisco / DataSF (.org, official) — e.g. cqi5-hm2d (Supplier Contracts)
|
|
183
|
+
"controllerdata.lacity.org", // LA City Controller (.org, official) — e.g. pggv-e4fn (Checkbook L.A.)
|
|
142
184
|
"opendata.usac.org", // USAC E-rate (.org, m6) — e.g. avi8-svp9
|
|
185
|
+
// ── County/city procurement sweep (loop cycle 17, 2026-07-20). Discovered via
|
|
186
|
+
// the Socrata federated catalog (api.us.socrata.com) filtered to procurement/
|
|
187
|
+
// bid/contract datasets, then each host-scoped $select=count(*) + /resource
|
|
188
|
+
// 200 bare-array live-verified. US local govs only (Canada/AU + demo/test +
|
|
189
|
+
// off-theme aggregate hosts filtered out). Mix of official .gov/.org/.com
|
|
190
|
+
// municipal portals (same documented non-.gov trust-boundary as above). ──
|
|
191
|
+
"data.kcmo.org", // Kansas City MO (.org, official) — e.g. 4mdg-usvj (Vendor Payments ~144k; 22 procurement datasets)
|
|
192
|
+
"data.brla.gov", // Baton Rouge / East Baton Rouge Parish LA (.gov) — e.g. e5pk-us93 (Upcoming Procurement Opportunities; 19 procurement datasets)
|
|
193
|
+
"www.dallasopendata.com", // Dallas TX (.com, official) — e.g. x5ih-idh7 (Vendor Payments FY2019–present ~166k)
|
|
194
|
+
"data.lacity.org", // Los Angeles CA (.org, official) — e.g. hf3r-utnq (RAMP Open Bid Opportunities — live bids)
|
|
195
|
+
"data.ramseycountymn.gov", // Ramsey County MN (.gov) — e.g. iu7r-dzmj (Solicitations & Addenda, with due_date/download_url ~516)
|
|
196
|
+
"data.richmondgov.com", // Richmond VA (.com, official) — e.g. xqn7-jvv2 (City Contracts: contract_value/supplier/procurement_type ~1,387)
|
|
143
197
|
] as const;
|
|
144
198
|
|
|
145
199
|
export type SocrataDomain = (typeof SOCRATA_DOMAINS)[number];
|
|
@@ -262,6 +316,44 @@ async function getCatalog(params: URLSearchParams): Promise<unknown> {
|
|
|
262
316
|
});
|
|
263
317
|
}
|
|
264
318
|
|
|
319
|
+
/**
|
|
320
|
+
* GET a HOST-SCOPED discovery catalog (`https://{domain}/api/catalog/v1?
|
|
321
|
+
* search_context={domain}&…`). Unlike the FEDERATED `getCatalog`
|
|
322
|
+
* (api.us.socrata.com), each portal's OWN catalog COMPLETELY indexes its own
|
|
323
|
+
* datasets — the federated aggregator under-indexes many hosts (loop cycle 8:
|
|
324
|
+
* USAC → 0, DOT → 3 of 1,873). Used whenever a specific `domain` is requested;
|
|
325
|
+
* the all-host search (domain omitted) still needs the federated aggregator.
|
|
326
|
+
* SSRF: identical guard to getSocrataResource — domain ∈ allowlist + the
|
|
327
|
+
* CONSTRUCTED URL's hostname === domain (https); redirect:"error" (B1); the
|
|
328
|
+
* host-only label carries the domain (never the token, m7).
|
|
329
|
+
*/
|
|
330
|
+
async function getHostCatalog(
|
|
331
|
+
domain: string,
|
|
332
|
+
params: URLSearchParams,
|
|
333
|
+
): Promise<unknown> {
|
|
334
|
+
if (!SOCRATA_DOMAIN_SET.has(domain)) {
|
|
335
|
+
throw new ToolErrorCarrier({
|
|
336
|
+
kind: "invalid_input",
|
|
337
|
+
message: `Socrata domain ${JSON.stringify(domain)} is not on the curated allowlist. Allowed: ${SOCRATA_DOMAINS.join(", ")}.`,
|
|
338
|
+
retryable: false,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const url = `https://${domain}/api/catalog/v1?${params.toString()}`;
|
|
342
|
+
const built = new URL(url);
|
|
343
|
+
if (built.hostname !== domain || built.protocol !== "https:") {
|
|
344
|
+
throw new ToolErrorCarrier({
|
|
345
|
+
kind: "invalid_input",
|
|
346
|
+
message: `Constructed Socrata host-catalog URL host ${JSON.stringify(built.hostname)} (${built.protocol}) does not match the allowlisted domain ${JSON.stringify(domain)} over https — refusing to fetch (SSRF safety).`,
|
|
347
|
+
retryable: false,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return getJson(url, {
|
|
351
|
+
label: "socrata:catalog:" + domain,
|
|
352
|
+
headers: appTokenHeader(),
|
|
353
|
+
redirect: "error",
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
265
357
|
// ─── map + meta helpers ───────────────────────────────────────────
|
|
266
358
|
const STRING_COERCION_NOTE =
|
|
267
359
|
"Row value fields arrive as strings verbatim from SODA (e.g. \"13650.00\", \"1\") — parse client-side. A missing value is absent, never 0.";
|
|
@@ -464,16 +556,21 @@ function mapCatalogRow(row: unknown): CatalogDataset {
|
|
|
464
556
|
}
|
|
465
557
|
|
|
466
558
|
/**
|
|
467
|
-
* Discover dataset 4x4 ids via the Socrata catalog (memoized ~10 min).
|
|
468
|
-
* `domain`
|
|
469
|
-
*
|
|
470
|
-
*
|
|
559
|
+
* Discover dataset 4x4 ids via the Socrata catalog (memoized ~10 min). Passing a
|
|
560
|
+
* `domain` queries that portal's OWN catalog (search_context) — a COMPLETE
|
|
561
|
+
* per-host index; omitting it searches the WHOLE allowlist via the federated
|
|
562
|
+
* api.us.socrata.com aggregator (repeated `domains=`). Returns `[{ id, name,
|
|
563
|
+
* description, domain, updatedAt, link }]` + `totalAvailable = resultSetSize`.
|
|
564
|
+
* Feeds `datasetId` to socrata_query.
|
|
471
565
|
*
|
|
472
566
|
* m3 — this is the catalog's PRIMARY response: a non-number `resultSetSize` is a
|
|
473
567
|
* hard schema_drift throw (nothing valid to return), never a fabricated total.
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
568
|
+
*
|
|
569
|
+
* UNDER-INDEX (loop cycle 8 fix): the FEDERATED aggregator under-indexes many
|
|
570
|
+
* hosts (USAC → 0; DOT → 3 of 1,873), so the all-host search (domain omitted) can
|
|
571
|
+
* miss datasets — its `_meta` note discloses this. A DOMAIN-scoped search now
|
|
572
|
+
* uses that host's OWN catalog, which indexes it completely (USAC/DOT/etc. fully
|
|
573
|
+
* discoverable). socrata_query works regardless with a known 4x4.
|
|
477
574
|
*/
|
|
478
575
|
export async function discoverDatasets(args: {
|
|
479
576
|
q: string;
|
|
@@ -485,8 +582,11 @@ export async function discoverDatasets(args: {
|
|
|
485
582
|
params.set("q", args.q);
|
|
486
583
|
params.set("only", "datasets");
|
|
487
584
|
params.set("limit", String(limit));
|
|
585
|
+
// A specific domain → that portal's OWN catalog (search_context), a COMPLETE
|
|
586
|
+
// per-host index. Omitting domain → the federated aggregator (repeated
|
|
587
|
+
// domains=), the only way to search across all hosts (loop cycle 8).
|
|
488
588
|
if (args.domain) {
|
|
489
|
-
params.
|
|
589
|
+
params.set("search_context", args.domain);
|
|
490
590
|
} else {
|
|
491
591
|
for (const d of SOCRATA_DOMAINS) params.append("domains", d);
|
|
492
592
|
}
|
|
@@ -495,7 +595,9 @@ export async function discoverDatasets(args: {
|
|
|
495
595
|
const { totalAvailable, results } = await memoize(
|
|
496
596
|
key,
|
|
497
597
|
async () => {
|
|
498
|
-
const body =
|
|
598
|
+
const body = args.domain
|
|
599
|
+
? await getHostCatalog(args.domain, params)
|
|
600
|
+
: await getCatalog(params);
|
|
499
601
|
const b = (body ?? {}) as { results?: unknown; resultSetSize?: unknown };
|
|
500
602
|
// m3 — hard drift on the PRIMARY response (contrast the best-effort count).
|
|
501
603
|
// The check stays INSIDE the memoize callback so a bad shape is never
|
|
@@ -517,6 +619,9 @@ export async function discoverDatasets(args: {
|
|
|
517
619
|
const scope = args.domain ? `domain ${args.domain}` : `the ${SOCRATA_DOMAINS.length}-host allowlist`;
|
|
518
620
|
const notes: string[] = [
|
|
519
621
|
`Catalog search over ${scope} (only=datasets). Feed a result's id to socrata_query as datasetId.`,
|
|
622
|
+
args.domain
|
|
623
|
+
? `Source: the ${args.domain} portal's OWN catalog (search_context) — a COMPLETE per-host index.`
|
|
624
|
+
: `Source: the federated ${CATALOG_HOST} aggregator, which UNDER-INDEXES some hosts (e.g. USAC, DOT) — pass a specific domain to use that host's own complete catalog.`,
|
|
520
625
|
`App token: ${appTokenPresent() ? "present (X-App-Token sent; value never logged)" : "absent (keyless)"}.`,
|
|
521
626
|
];
|
|
522
627
|
if (totalAvailable !== null && returned < totalAvailable) {
|
|
@@ -528,7 +633,7 @@ export async function discoverDatasets(args: {
|
|
|
528
633
|
return withMeta(
|
|
529
634
|
{ query: args.q, domain: args.domain ?? null, results },
|
|
530
635
|
{
|
|
531
|
-
source: `${CATALOG_HOST} catalog ${SOURCE_SUFFIX}`,
|
|
636
|
+
source: `${args.domain ?? CATALOG_HOST} catalog ${SOURCE_SUFFIX}`,
|
|
532
637
|
keylessMode: true,
|
|
533
638
|
returned,
|
|
534
639
|
totalAvailable,
|