us-gov-open-data-mcp 1.0.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.md +211 -0
- package/dist/client.d.ts +54 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +388 -0
- package/dist/client.js.map +1 -0
- package/dist/instructions.d.ts +8 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +549 -0
- package/dist/instructions.js.map +1 -0
- package/dist/modules/bea.d.ts +28 -0
- package/dist/modules/bea.d.ts.map +1 -0
- package/dist/modules/bea.js +158 -0
- package/dist/modules/bea.js.map +1 -0
- package/dist/modules/bls.d.ts +29 -0
- package/dist/modules/bls.d.ts.map +1 -0
- package/dist/modules/bls.js +244 -0
- package/dist/modules/bls.js.map +1 -0
- package/dist/modules/bts.d.ts +26 -0
- package/dist/modules/bts.d.ts.map +1 -0
- package/dist/modules/bts.js +112 -0
- package/dist/modules/bts.js.map +1 -0
- package/dist/modules/cdc.d.ts +26 -0
- package/dist/modules/cdc.d.ts.map +1 -0
- package/dist/modules/cdc.js +310 -0
- package/dist/modules/cdc.js.map +1 -0
- package/dist/modules/census.d.ts +23 -0
- package/dist/modules/census.d.ts.map +1 -0
- package/dist/modules/census.js +141 -0
- package/dist/modules/census.js.map +1 -0
- package/dist/modules/cfpb.d.ts +20 -0
- package/dist/modules/cfpb.d.ts.map +1 -0
- package/dist/modules/cfpb.js +135 -0
- package/dist/modules/cfpb.js.map +1 -0
- package/dist/modules/clinical-trials.d.ts +22 -0
- package/dist/modules/clinical-trials.d.ts.map +1 -0
- package/dist/modules/clinical-trials.js +171 -0
- package/dist/modules/clinical-trials.js.map +1 -0
- package/dist/modules/cms.d.ts +21 -0
- package/dist/modules/cms.d.ts.map +1 -0
- package/dist/modules/cms.js +135 -0
- package/dist/modules/cms.js.map +1 -0
- package/dist/modules/college-scorecard.d.ts +39 -0
- package/dist/modules/college-scorecard.d.ts.map +1 -0
- package/dist/modules/college-scorecard.js +192 -0
- package/dist/modules/college-scorecard.js.map +1 -0
- package/dist/modules/congress.d.ts +28 -0
- package/dist/modules/congress.d.ts.map +1 -0
- package/dist/modules/congress.js +883 -0
- package/dist/modules/congress.js.map +1 -0
- package/dist/modules/dol.d.ts +27 -0
- package/dist/modules/dol.d.ts.map +1 -0
- package/dist/modules/dol.js +209 -0
- package/dist/modules/dol.js.map +1 -0
- package/dist/modules/eia.d.ts +33 -0
- package/dist/modules/eia.d.ts.map +1 -0
- package/dist/modules/eia.js +227 -0
- package/dist/modules/eia.js.map +1 -0
- package/dist/modules/epa.d.ts +21 -0
- package/dist/modules/epa.d.ts.map +1 -0
- package/dist/modules/epa.js +91 -0
- package/dist/modules/epa.js.map +1 -0
- package/dist/modules/fbi.d.ts +28 -0
- package/dist/modules/fbi.d.ts.map +1 -0
- package/dist/modules/fbi.js +143 -0
- package/dist/modules/fbi.js.map +1 -0
- package/dist/modules/fda.d.ts +35 -0
- package/dist/modules/fda.d.ts.map +1 -0
- package/dist/modules/fda.js +262 -0
- package/dist/modules/fda.js.map +1 -0
- package/dist/modules/fdic.d.ts +21 -0
- package/dist/modules/fdic.d.ts.map +1 -0
- package/dist/modules/fdic.js +186 -0
- package/dist/modules/fdic.js.map +1 -0
- package/dist/modules/fec.d.ts +29 -0
- package/dist/modules/fec.d.ts.map +1 -0
- package/dist/modules/fec.js +282 -0
- package/dist/modules/fec.js.map +1 -0
- package/dist/modules/federal-register.d.ts +24 -0
- package/dist/modules/federal-register.d.ts.map +1 -0
- package/dist/modules/federal-register.js +184 -0
- package/dist/modules/federal-register.js.map +1 -0
- package/dist/modules/fema.d.ts +20 -0
- package/dist/modules/fema.d.ts.map +1 -0
- package/dist/modules/fema.js +156 -0
- package/dist/modules/fema.js.map +1 -0
- package/dist/modules/fred.d.ts +40 -0
- package/dist/modules/fred.d.ts.map +1 -0
- package/dist/modules/fred.js +143 -0
- package/dist/modules/fred.js.map +1 -0
- package/dist/modules/govinfo.d.ts +24 -0
- package/dist/modules/govinfo.d.ts.map +1 -0
- package/dist/modules/govinfo.js +147 -0
- package/dist/modules/govinfo.js.map +1 -0
- package/dist/modules/hud.d.ts +17 -0
- package/dist/modules/hud.d.ts.map +1 -0
- package/dist/modules/hud.js +170 -0
- package/dist/modules/hud.js.map +1 -0
- package/dist/modules/naep.d.ts +27 -0
- package/dist/modules/naep.d.ts.map +1 -0
- package/dist/modules/naep.js +210 -0
- package/dist/modules/naep.js.map +1 -0
- package/dist/modules/nhtsa.d.ts +13 -0
- package/dist/modules/nhtsa.d.ts.map +1 -0
- package/dist/modules/nhtsa.js +196 -0
- package/dist/modules/nhtsa.js.map +1 -0
- package/dist/modules/noaa.d.ts +41 -0
- package/dist/modules/noaa.d.ts.map +1 -0
- package/dist/modules/noaa.js +135 -0
- package/dist/modules/noaa.js.map +1 -0
- package/dist/modules/nrel.d.ts +25 -0
- package/dist/modules/nrel.d.ts.map +1 -0
- package/dist/modules/nrel.js +87 -0
- package/dist/modules/nrel.js.map +1 -0
- package/dist/modules/regulations.d.ts +24 -0
- package/dist/modules/regulations.d.ts.map +1 -0
- package/dist/modules/regulations.js +173 -0
- package/dist/modules/regulations.js.map +1 -0
- package/dist/modules/sec.d.ts +25 -0
- package/dist/modules/sec.d.ts.map +1 -0
- package/dist/modules/sec.js +192 -0
- package/dist/modules/sec.js.map +1 -0
- package/dist/modules/senate-lobbying.d.ts +21 -0
- package/dist/modules/senate-lobbying.d.ts.map +1 -0
- package/dist/modules/senate-lobbying.js +189 -0
- package/dist/modules/senate-lobbying.js.map +1 -0
- package/dist/modules/treasury.d.ts +23 -0
- package/dist/modules/treasury.d.ts.map +1 -0
- package/dist/modules/treasury.js +234 -0
- package/dist/modules/treasury.js.map +1 -0
- package/dist/modules/usaspending.d.ts +19 -0
- package/dist/modules/usaspending.d.ts.map +1 -0
- package/dist/modules/usaspending.js +204 -0
- package/dist/modules/usaspending.js.map +1 -0
- package/dist/modules/usda-fooddata.d.ts +24 -0
- package/dist/modules/usda-fooddata.d.ts.map +1 -0
- package/dist/modules/usda-fooddata.js +118 -0
- package/dist/modules/usda-fooddata.js.map +1 -0
- package/dist/modules/usda-nass.d.ts +46 -0
- package/dist/modules/usda-nass.d.ts.map +1 -0
- package/dist/modules/usda-nass.js +151 -0
- package/dist/modules/usda-nass.js.map +1 -0
- package/dist/modules/usgs.d.ts +21 -0
- package/dist/modules/usgs.d.ts.map +1 -0
- package/dist/modules/usgs.js +203 -0
- package/dist/modules/usgs.js.map +1 -0
- package/dist/modules/uspto.d.ts +13 -0
- package/dist/modules/uspto.d.ts.map +1 -0
- package/dist/modules/uspto.js +157 -0
- package/dist/modules/uspto.js.map +1 -0
- package/dist/modules/world-bank.d.ts +21 -0
- package/dist/modules/world-bank.d.ts.map +1 -0
- package/dist/modules/world-bank.js +130 -0
- package/dist/modules/world-bank.js.map +1 -0
- package/dist/prompts.d.ts +12 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +858 -0
- package/dist/prompts.js.map +1 -0
- package/dist/sdk/bea.d.ts +111 -0
- package/dist/sdk/bea.d.ts.map +1 -0
- package/dist/sdk/bea.js +242 -0
- package/dist/sdk/bea.js.map +1 -0
- package/dist/sdk/bls.d.ts +65 -0
- package/dist/sdk/bls.d.ts.map +1 -0
- package/dist/sdk/bls.js +203 -0
- package/dist/sdk/bls.js.map +1 -0
- package/dist/sdk/bts.d.ts +108 -0
- package/dist/sdk/bts.d.ts.map +1 -0
- package/dist/sdk/bts.js +121 -0
- package/dist/sdk/bts.js.map +1 -0
- package/dist/sdk/cdc.d.ts +105 -0
- package/dist/sdk/cdc.d.ts.map +1 -0
- package/dist/sdk/cdc.js +222 -0
- package/dist/sdk/cdc.js.map +1 -0
- package/dist/sdk/census.d.ts +47 -0
- package/dist/sdk/census.d.ts.map +1 -0
- package/dist/sdk/census.js +99 -0
- package/dist/sdk/census.js.map +1 -0
- package/dist/sdk/cfpb.d.ts +148 -0
- package/dist/sdk/cfpb.d.ts.map +1 -0
- package/dist/sdk/cfpb.js +153 -0
- package/dist/sdk/cfpb.js.map +1 -0
- package/dist/sdk/clinical-trials.d.ts +214 -0
- package/dist/sdk/clinical-trials.d.ts.map +1 -0
- package/dist/sdk/clinical-trials.js +134 -0
- package/dist/sdk/clinical-trials.js.map +1 -0
- package/dist/sdk/cms.d.ts +81 -0
- package/dist/sdk/cms.d.ts.map +1 -0
- package/dist/sdk/cms.js +227 -0
- package/dist/sdk/cms.js.map +1 -0
- package/dist/sdk/college-scorecard.d.ts +63 -0
- package/dist/sdk/college-scorecard.d.ts.map +1 -0
- package/dist/sdk/college-scorecard.js +131 -0
- package/dist/sdk/college-scorecard.js.map +1 -0
- package/dist/sdk/congress.d.ts +575 -0
- package/dist/sdk/congress.d.ts.map +1 -0
- package/dist/sdk/congress.js +659 -0
- package/dist/sdk/congress.js.map +1 -0
- package/dist/sdk/dol.d.ts +299 -0
- package/dist/sdk/dol.d.ts.map +1 -0
- package/dist/sdk/dol.js +252 -0
- package/dist/sdk/dol.js.map +1 -0
- package/dist/sdk/eia.d.ts +91 -0
- package/dist/sdk/eia.d.ts.map +1 -0
- package/dist/sdk/eia.js +156 -0
- package/dist/sdk/eia.js.map +1 -0
- package/dist/sdk/epa.d.ts +128 -0
- package/dist/sdk/epa.d.ts.map +1 -0
- package/dist/sdk/epa.js +120 -0
- package/dist/sdk/epa.js.map +1 -0
- package/dist/sdk/fbi.d.ts +48 -0
- package/dist/sdk/fbi.d.ts.map +1 -0
- package/dist/sdk/fbi.js +69 -0
- package/dist/sdk/fbi.js.map +1 -0
- package/dist/sdk/fda.d.ts +356 -0
- package/dist/sdk/fda.d.ts.map +1 -0
- package/dist/sdk/fda.js +162 -0
- package/dist/sdk/fda.js.map +1 -0
- package/dist/sdk/fdic.d.ts +227 -0
- package/dist/sdk/fdic.d.ts.map +1 -0
- package/dist/sdk/fdic.js +172 -0
- package/dist/sdk/fdic.js.map +1 -0
- package/dist/sdk/fec.d.ts +142 -0
- package/dist/sdk/fec.d.ts.map +1 -0
- package/dist/sdk/fec.js +92 -0
- package/dist/sdk/fec.js.map +1 -0
- package/dist/sdk/federal-register.d.ts +88 -0
- package/dist/sdk/federal-register.d.ts.map +1 -0
- package/dist/sdk/federal-register.js +100 -0
- package/dist/sdk/federal-register.js.map +1 -0
- package/dist/sdk/fema.d.ts +137 -0
- package/dist/sdk/fema.d.ts.map +1 -0
- package/dist/sdk/fema.js +197 -0
- package/dist/sdk/fema.js.map +1 -0
- package/dist/sdk/fred.d.ts +72 -0
- package/dist/sdk/fred.d.ts.map +1 -0
- package/dist/sdk/fred.js +59 -0
- package/dist/sdk/fred.js.map +1 -0
- package/dist/sdk/govinfo.d.ts +64 -0
- package/dist/sdk/govinfo.d.ts.map +1 -0
- package/dist/sdk/govinfo.js +187 -0
- package/dist/sdk/govinfo.js.map +1 -0
- package/dist/sdk/hud.d.ts +87 -0
- package/dist/sdk/hud.d.ts.map +1 -0
- package/dist/sdk/hud.js +91 -0
- package/dist/sdk/hud.js.map +1 -0
- package/dist/sdk/index.d.ts +51 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +51 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/sdk/naep.d.ts +93 -0
- package/dist/sdk/naep.d.ts.map +1 -0
- package/dist/sdk/naep.js +163 -0
- package/dist/sdk/naep.js.map +1 -0
- package/dist/sdk/nhtsa.d.ts +169 -0
- package/dist/sdk/nhtsa.d.ts.map +1 -0
- package/dist/sdk/nhtsa.js +102 -0
- package/dist/sdk/nhtsa.js.map +1 -0
- package/dist/sdk/noaa.d.ts +72 -0
- package/dist/sdk/noaa.d.ts.map +1 -0
- package/dist/sdk/noaa.js +64 -0
- package/dist/sdk/noaa.js.map +1 -0
- package/dist/sdk/nrel.d.ts +145 -0
- package/dist/sdk/nrel.d.ts.map +1 -0
- package/dist/sdk/nrel.js +93 -0
- package/dist/sdk/nrel.js.map +1 -0
- package/dist/sdk/regulations.d.ts +146 -0
- package/dist/sdk/regulations.d.ts.map +1 -0
- package/dist/sdk/regulations.js +103 -0
- package/dist/sdk/regulations.js.map +1 -0
- package/dist/sdk/sec.d.ts +114 -0
- package/dist/sdk/sec.d.ts.map +1 -0
- package/dist/sdk/sec.js +151 -0
- package/dist/sdk/sec.js.map +1 -0
- package/dist/sdk/senate-lobbying.d.ts +147 -0
- package/dist/sdk/senate-lobbying.d.ts.map +1 -0
- package/dist/sdk/senate-lobbying.js +125 -0
- package/dist/sdk/senate-lobbying.js.map +1 -0
- package/dist/sdk/treasury.d.ts +59 -0
- package/dist/sdk/treasury.d.ts.map +1 -0
- package/dist/sdk/treasury.js +1397 -0
- package/dist/sdk/treasury.js.map +1 -0
- package/dist/sdk/usaspending.d.ts +126 -0
- package/dist/sdk/usaspending.d.ts.map +1 -0
- package/dist/sdk/usaspending.js +270 -0
- package/dist/sdk/usaspending.js.map +1 -0
- package/dist/sdk/usda-fooddata.d.ts +112 -0
- package/dist/sdk/usda-fooddata.d.ts.map +1 -0
- package/dist/sdk/usda-fooddata.js +80 -0
- package/dist/sdk/usda-fooddata.js.map +1 -0
- package/dist/sdk/usda-nass.d.ts +75 -0
- package/dist/sdk/usda-nass.d.ts.map +1 -0
- package/dist/sdk/usda-nass.js +83 -0
- package/dist/sdk/usda-nass.js.map +1 -0
- package/dist/sdk/usgs.d.ts +221 -0
- package/dist/sdk/usgs.d.ts.map +1 -0
- package/dist/sdk/usgs.js +182 -0
- package/dist/sdk/usgs.js.map +1 -0
- package/dist/sdk/uspto.d.ts +109 -0
- package/dist/sdk/uspto.d.ts.map +1 -0
- package/dist/sdk/uspto.js +286 -0
- package/dist/sdk/uspto.js.map +1 -0
- package/dist/sdk/world-bank.d.ts +78 -0
- package/dist/sdk/world-bank.d.ts.map +1 -0
- package/dist/sdk/world-bank.js +72 -0
- package/dist/sdk/world-bank.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +196 -0
- package/dist/server.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Congress.gov SDK — typed API client for the Congress.gov API (v3).
|
|
3
|
+
*
|
|
4
|
+
* Standalone — no MCP server required. Usage:
|
|
5
|
+
*
|
|
6
|
+
* import { searchBills, getBillDetails } from "us-gov-open-data/sdk/congress";
|
|
7
|
+
*
|
|
8
|
+
* Requires DATA_GOV_API_KEY env var. Get one at https://api.data.gov/signup/
|
|
9
|
+
*/
|
|
10
|
+
import { createClient } from "../client.js";
|
|
11
|
+
// ─── Client ──────────────────────────────────────────────────────────
|
|
12
|
+
const api = createClient({
|
|
13
|
+
baseUrl: "https://api.congress.gov/v3",
|
|
14
|
+
name: "congress",
|
|
15
|
+
auth: {
|
|
16
|
+
type: "query",
|
|
17
|
+
key: "api_key",
|
|
18
|
+
envVar: "DATA_GOV_API_KEY",
|
|
19
|
+
extraParams: { format: "json" },
|
|
20
|
+
},
|
|
21
|
+
rateLimit: { perSecond: 5, burst: 10 },
|
|
22
|
+
cacheTtlMs: 30 * 60 * 1000, // 30 min
|
|
23
|
+
});
|
|
24
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
25
|
+
/** Current congress number based on date. */
|
|
26
|
+
export function currentCongress() {
|
|
27
|
+
const year = new Date().getFullYear();
|
|
28
|
+
return Math.floor((year - 1789) / 2) + 1;
|
|
29
|
+
}
|
|
30
|
+
/** Map bill type codes to Congress.gov URL segments. */
|
|
31
|
+
const BILL_URL_SEGMENTS = {
|
|
32
|
+
hr: "house-bill",
|
|
33
|
+
s: "senate-bill",
|
|
34
|
+
hjres: "house-joint-resolution",
|
|
35
|
+
sjres: "senate-joint-resolution",
|
|
36
|
+
hconres: "house-concurrent-resolution",
|
|
37
|
+
sconres: "senate-concurrent-resolution",
|
|
38
|
+
hres: "house-resolution",
|
|
39
|
+
sres: "senate-resolution",
|
|
40
|
+
};
|
|
41
|
+
export function billTypeToUrlSegment(billType) {
|
|
42
|
+
return BILL_URL_SEGMENTS[billType.toLowerCase()] || "house-bill";
|
|
43
|
+
}
|
|
44
|
+
// ─── Reference data ──────────────────────────────────────────────────
|
|
45
|
+
export const billTypes = {
|
|
46
|
+
hr: { name: "House Bill", urlSegment: "house-bill" },
|
|
47
|
+
s: { name: "Senate Bill", urlSegment: "senate-bill" },
|
|
48
|
+
hjres: { name: "House Joint Resolution", urlSegment: "house-joint-resolution" },
|
|
49
|
+
sjres: { name: "Senate Joint Resolution", urlSegment: "senate-joint-resolution" },
|
|
50
|
+
hconres: { name: "House Concurrent Resolution", urlSegment: "house-concurrent-resolution" },
|
|
51
|
+
sconres: { name: "Senate Concurrent Resolution", urlSegment: "senate-concurrent-resolution" },
|
|
52
|
+
hres: { name: "House Simple Resolution", urlSegment: "house-resolution" },
|
|
53
|
+
sres: { name: "Senate Simple Resolution", urlSegment: "senate-resolution" },
|
|
54
|
+
};
|
|
55
|
+
export const congressNumbers = {
|
|
56
|
+
119: "2025-2026", 118: "2023-2024", 117: "2021-2022",
|
|
57
|
+
116: "2019-2020", 115: "2017-2018", 114: "2015-2016",
|
|
58
|
+
113: "2013-2014", 112: "2011-2012", 111: "2009-2010",
|
|
59
|
+
};
|
|
60
|
+
// ─── Public API ──────────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Search/list bills by congress number and/or bill type.
|
|
63
|
+
*
|
|
64
|
+
* NOTE: The Congress.gov API v3 does NOT support text/keyword search on the /bill endpoint.
|
|
65
|
+
* The `query` parameter is accepted but used for client-side title filtering only.
|
|
66
|
+
* To find specific bills, use `getBillDetails()` with known bill numbers, or browse
|
|
67
|
+
* by congress/bill_type and filter results.
|
|
68
|
+
*/
|
|
69
|
+
export async function searchBills(opts = {}) {
|
|
70
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
71
|
+
let path = `/bill/${congressNum}`;
|
|
72
|
+
if (opts.bill_type)
|
|
73
|
+
path += `/${opts.bill_type.toLowerCase()}`;
|
|
74
|
+
// Fetch more if we need to filter client-side
|
|
75
|
+
const fetchLimit = opts.query ? Math.min((opts.limit ?? 20) * 5, 250) : (opts.limit ?? 20);
|
|
76
|
+
const res = await api.get(path, {
|
|
77
|
+
limit: fetchLimit,
|
|
78
|
+
offset: opts.offset ?? 0,
|
|
79
|
+
sort: "updateDate+desc",
|
|
80
|
+
});
|
|
81
|
+
let bills = res.bills ?? [];
|
|
82
|
+
// Client-side keyword filtering since the API doesn't support text search
|
|
83
|
+
if (opts.query) {
|
|
84
|
+
const terms = opts.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
85
|
+
bills = bills.filter(b => {
|
|
86
|
+
const title = (b.title ?? "").toLowerCase();
|
|
87
|
+
return terms.some(t => title.includes(t));
|
|
88
|
+
});
|
|
89
|
+
bills = bills.slice(0, opts.limit ?? 20);
|
|
90
|
+
}
|
|
91
|
+
return { bills };
|
|
92
|
+
}
|
|
93
|
+
/** Get detailed information about a specific bill, including cosponsors with party breakdown. */
|
|
94
|
+
export async function getBillDetails(congress, billType, billNumber) {
|
|
95
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}`);
|
|
96
|
+
const bill = res.bill ?? res;
|
|
97
|
+
// Secondary call for cosponsors
|
|
98
|
+
let cosponsors = [];
|
|
99
|
+
const cosponsorPartyBreakdown = {};
|
|
100
|
+
try {
|
|
101
|
+
const cRes = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/cosponsors`, { limit: 250 });
|
|
102
|
+
cosponsors = cRes.cosponsors ?? [];
|
|
103
|
+
for (const c of cosponsors) {
|
|
104
|
+
const party = (c.party ?? "?");
|
|
105
|
+
cosponsorPartyBreakdown[party] = (cosponsorPartyBreakdown[party] ?? 0) + 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Non-critical — some bills may not have cosponsors
|
|
110
|
+
}
|
|
111
|
+
return { bill, cosponsors, cosponsorPartyBreakdown };
|
|
112
|
+
}
|
|
113
|
+
/** Search members of Congress by state, congress, and/or district. */
|
|
114
|
+
export async function searchMembers(opts = {}) {
|
|
115
|
+
let path;
|
|
116
|
+
if (opts.state && opts.district !== undefined) {
|
|
117
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
118
|
+
path = `/member/congress/${congressNum}/${opts.state.toUpperCase()}/${opts.district}`;
|
|
119
|
+
}
|
|
120
|
+
else if (opts.state) {
|
|
121
|
+
path = `/member/${opts.state.toUpperCase()}`;
|
|
122
|
+
}
|
|
123
|
+
else if (opts.congress) {
|
|
124
|
+
path = `/member/congress/${opts.congress}`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
path = `/member`;
|
|
128
|
+
}
|
|
129
|
+
const res = await api.get(path, { limit: opts.limit ?? 50 });
|
|
130
|
+
return { members: res.members ?? [] };
|
|
131
|
+
}
|
|
132
|
+
/** Convert a calendar year to Congress number and session. */
|
|
133
|
+
function yearToCongress(year) {
|
|
134
|
+
const congress = Math.floor((year - 1789) / 2) + 1;
|
|
135
|
+
const session = (year % 2 === 1 ? 1 : 2);
|
|
136
|
+
return { congress, session };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get House roll call votes.
|
|
140
|
+
*
|
|
141
|
+
* Primary source: Congress.gov API (currently 118th–119th Congress, beta).
|
|
142
|
+
* Fallback: clerk.house.gov XML (coverage: 1990 to present) — fills gaps
|
|
143
|
+
* where the API returns no data (e.g. older congresses).
|
|
144
|
+
*
|
|
145
|
+
* Data sources:
|
|
146
|
+
* - https://api.congress.gov/v3/house-vote
|
|
147
|
+
* - https://clerk.house.gov/Votes
|
|
148
|
+
*/
|
|
149
|
+
export async function getHouseVotes(opts = {}) {
|
|
150
|
+
// Resolve congress+session from year if needed (API uses congress/session)
|
|
151
|
+
const resolvedOpts = { ...opts };
|
|
152
|
+
if (opts.year && !opts.congress) {
|
|
153
|
+
const { congress, session } = yearToCongress(opts.year);
|
|
154
|
+
resolvedOpts.congress = congress;
|
|
155
|
+
resolvedOpts.session = opts.session ?? session;
|
|
156
|
+
}
|
|
157
|
+
// Try Congress.gov API first
|
|
158
|
+
try {
|
|
159
|
+
const apiResult = await getHouseVotesFromApi(resolvedOpts);
|
|
160
|
+
const hasData = opts.vote_number
|
|
161
|
+
? (apiResult.members?.length ?? 0) > 0 || apiResult.vote != null
|
|
162
|
+
: (apiResult.votes?.length ?? 0) > 0;
|
|
163
|
+
if (hasData)
|
|
164
|
+
return apiResult;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// API error — continue to clerk fallback
|
|
168
|
+
}
|
|
169
|
+
// Fall back to clerk.house.gov XML (1990–present)
|
|
170
|
+
return getHouseVotesFromClerk(opts);
|
|
171
|
+
}
|
|
172
|
+
/** Fallback: Fetch House votes from clerk.house.gov XML (1990–present). */
|
|
173
|
+
async function getHouseVotesFromClerk(opts) {
|
|
174
|
+
// Resolve year (either explicit or derived from congress+session)
|
|
175
|
+
let year;
|
|
176
|
+
if (opts.year) {
|
|
177
|
+
year = opts.year;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
181
|
+
const sessionNum = opts.session ?? currentSession();
|
|
182
|
+
year = congressSessionToYear(congressNum, sessionNum);
|
|
183
|
+
}
|
|
184
|
+
if (opts.vote_number) {
|
|
185
|
+
// Fetch individual vote XML from clerk.house.gov
|
|
186
|
+
const num = String(opts.vote_number).padStart(3, "0");
|
|
187
|
+
const url = `${HOUSE_CLERK_BASE}/${year}/roll${num}.xml`;
|
|
188
|
+
const resp = await fetch(url, {
|
|
189
|
+
headers: { "User-Agent": "us-gov-open-data-mcp/2.0 (gov-accountability-tool)" },
|
|
190
|
+
signal: AbortSignal.timeout(30_000),
|
|
191
|
+
});
|
|
192
|
+
if (!resp.ok)
|
|
193
|
+
throw new Error(`House vote fetch failed: ${resp.status} ${resp.statusText} (${url})`);
|
|
194
|
+
const xml = await resp.text();
|
|
195
|
+
const parsed = parseXml(xml);
|
|
196
|
+
const rc = (parsed["rollcall-vote"] ?? {});
|
|
197
|
+
const meta = (rc["vote-metadata"] ?? {});
|
|
198
|
+
const voteDataNode = (rc["vote-data"] ?? {});
|
|
199
|
+
// Build vote summary
|
|
200
|
+
const vote = {
|
|
201
|
+
rollCallNumber: Number(meta["rollcall-num"]) || opts.vote_number,
|
|
202
|
+
date: String(meta["action-date"] ?? ""),
|
|
203
|
+
question: String(meta["vote-question"] ?? ""),
|
|
204
|
+
voteQuestion: String(meta["vote-question"] ?? ""),
|
|
205
|
+
result: String(meta["vote-result"] ?? ""),
|
|
206
|
+
description: String(meta["vote-desc"] ?? ""),
|
|
207
|
+
legislationNumber: String(meta["legis-num"] ?? ""),
|
|
208
|
+
voteType: String(meta["vote-type"] ?? ""),
|
|
209
|
+
};
|
|
210
|
+
// Parse members from recorded-vote entries
|
|
211
|
+
const recordedVotes = (voteDataNode["recorded-vote"] ?? []);
|
|
212
|
+
const members = recordedVotes.map((rv) => {
|
|
213
|
+
const leg = (rv.legislator ?? {});
|
|
214
|
+
return {
|
|
215
|
+
bioguideID: String(leg["@_name-id"] ?? ""),
|
|
216
|
+
lastName: String(leg["@_sort-field"] ?? leg["@_unaccented-name"] ?? ""),
|
|
217
|
+
firstName: "",
|
|
218
|
+
voteParty: String(leg["@_party"] ?? ""),
|
|
219
|
+
party: String(leg["@_party"] ?? ""),
|
|
220
|
+
voteState: String(leg["@_state"] ?? ""),
|
|
221
|
+
voteCast: String(rv.vote ?? ""),
|
|
222
|
+
votePosition: String(rv.vote ?? ""),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
// Build party tally
|
|
226
|
+
const partyTally = {};
|
|
227
|
+
for (const m of members) {
|
|
228
|
+
const p = m.party ?? "?";
|
|
229
|
+
const v = m.votePosition ?? "?";
|
|
230
|
+
if (!partyTally[p])
|
|
231
|
+
partyTally[p] = {};
|
|
232
|
+
partyTally[p][v] = (partyTally[p][v] ?? 0) + 1;
|
|
233
|
+
}
|
|
234
|
+
return { vote, members, partyTally, source: "clerk.house.gov" };
|
|
235
|
+
}
|
|
236
|
+
// List votes: parse HTML index page from clerk.house.gov
|
|
237
|
+
const url = `${HOUSE_CLERK_BASE}/${year}/index.asp`;
|
|
238
|
+
const resp = await fetch(url, {
|
|
239
|
+
headers: { "User-Agent": "us-gov-open-data-mcp/2.0 (gov-accountability-tool)" },
|
|
240
|
+
signal: AbortSignal.timeout(30_000),
|
|
241
|
+
});
|
|
242
|
+
if (!resp.ok)
|
|
243
|
+
throw new Error(`House vote index fetch failed: ${resp.status} (${url})`);
|
|
244
|
+
const html = await resp.text();
|
|
245
|
+
const votes = parseHouseVoteIndex(html, opts.limit ?? 20);
|
|
246
|
+
return { votes, source: "clerk.house.gov" };
|
|
247
|
+
}
|
|
248
|
+
/** Primary: Fetch House votes from Congress.gov API (118th–119th Congress, beta). */
|
|
249
|
+
async function getHouseVotesFromApi(opts) {
|
|
250
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
251
|
+
if (opts.vote_number && opts.session) {
|
|
252
|
+
// Try member-level breakdown first
|
|
253
|
+
try {
|
|
254
|
+
const res = await api.get(`/house-vote/${congressNum}/${opts.session}/${opts.vote_number}/members`);
|
|
255
|
+
const raw = res.houseRollCallVoteMemberVotes?.results ?? [];
|
|
256
|
+
const members = raw.map((m) => ({
|
|
257
|
+
...m,
|
|
258
|
+
party: m.voteParty,
|
|
259
|
+
votePosition: m.voteCast,
|
|
260
|
+
}));
|
|
261
|
+
const partyTally = {};
|
|
262
|
+
for (const m of members) {
|
|
263
|
+
const party = (m.party ?? "?");
|
|
264
|
+
const pos = (m.votePosition ?? "?");
|
|
265
|
+
if (!partyTally[party])
|
|
266
|
+
partyTally[party] = {};
|
|
267
|
+
partyTally[party][pos] = (partyTally[party][pos] ?? 0) + 1;
|
|
268
|
+
}
|
|
269
|
+
return { members, partyTally, source: "api.congress.gov" };
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Fall back to vote summary
|
|
273
|
+
const vRes = await api.get(`/house-vote/${congressNum}/${opts.session}/${opts.vote_number}`);
|
|
274
|
+
const vote = vRes.houseRollCallVote ?? vRes;
|
|
275
|
+
if (vote.votePartyTotal) {
|
|
276
|
+
const partyTally = {};
|
|
277
|
+
for (const pt of vote.votePartyTotal) {
|
|
278
|
+
partyTally[pt.voteParty] = {
|
|
279
|
+
Yea: pt.yeaTotal,
|
|
280
|
+
Nay: pt.nayTotal,
|
|
281
|
+
"Not Voting": pt.notVotingTotal,
|
|
282
|
+
Present: pt.presentTotal,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return { vote, partyTally, source: "api.congress.gov" };
|
|
286
|
+
}
|
|
287
|
+
return { vote, source: "api.congress.gov" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// List recent votes via API
|
|
291
|
+
let path = `/house-vote/${congressNum}`;
|
|
292
|
+
if (opts.session)
|
|
293
|
+
path += `/${opts.session}`;
|
|
294
|
+
const res = await api.get(path, { limit: opts.limit ?? 20 });
|
|
295
|
+
return { votes: res.houseRollCallVotes ?? [], source: "api.congress.gov" };
|
|
296
|
+
}
|
|
297
|
+
/** Parse the clerk.house.gov HTML index page to extract a vote list. */
|
|
298
|
+
function parseHouseVoteIndex(html, limit) {
|
|
299
|
+
const votes = [];
|
|
300
|
+
// Match each table row that contains vote data
|
|
301
|
+
const rowPattern = /<TR>\s*<TD>[\s\S]*?<\/TR>/gi;
|
|
302
|
+
let rowMatch;
|
|
303
|
+
while ((rowMatch = rowPattern.exec(html)) !== null && votes.length < limit) {
|
|
304
|
+
const row = rowMatch[0];
|
|
305
|
+
// Extract TD cells — content may contain nested tags
|
|
306
|
+
const cellPattern = /<TD[^>]*>([\s\S]*?)<\/TD>/gi;
|
|
307
|
+
const cells = [];
|
|
308
|
+
let cellMatch;
|
|
309
|
+
while ((cellMatch = cellPattern.exec(row)) !== null) {
|
|
310
|
+
// Strip all HTML tags, collapse whitespace
|
|
311
|
+
cells.push(cellMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim());
|
|
312
|
+
}
|
|
313
|
+
if (cells.length < 6)
|
|
314
|
+
continue;
|
|
315
|
+
const rollNum = Number(cells[0]);
|
|
316
|
+
if (!rollNum)
|
|
317
|
+
continue;
|
|
318
|
+
const resultCode = cells[4].trim();
|
|
319
|
+
const result = resultCode === "P" ? "Passed" : resultCode === "F" ? "Failed" : resultCode === "A" ? "Agreed to" : resultCode;
|
|
320
|
+
votes.push({
|
|
321
|
+
rollCallNumber: rollNum,
|
|
322
|
+
date: cells[1],
|
|
323
|
+
question: cells[3],
|
|
324
|
+
result,
|
|
325
|
+
description: cells[5],
|
|
326
|
+
legislationNumber: cells[2],
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return votes;
|
|
330
|
+
}
|
|
331
|
+
/** Get recently enacted laws. */
|
|
332
|
+
export async function getRecentLaws(opts = {}) {
|
|
333
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
334
|
+
const res = await api.get(`/law/${congressNum}`, { limit: opts.limit ?? 20 });
|
|
335
|
+
return { laws: res.bills ?? res.laws ?? [] };
|
|
336
|
+
}
|
|
337
|
+
/** Get bills sponsored or cosponsored by a specific member. */
|
|
338
|
+
export async function getMemberBills(bioguideId, type = "sponsored", limit = 20) {
|
|
339
|
+
const legType = type === "cosponsored" ? "cosponsored-legislation" : "sponsored-legislation";
|
|
340
|
+
const res = await api.get(`/member/${bioguideId}/${legType}`, { limit });
|
|
341
|
+
return { bills: res.sponsoredLegislation ?? res.cosponsoredLegislation ?? [] };
|
|
342
|
+
}
|
|
343
|
+
// ─── Bill Sub-resource Methods ───────────────────────────────────────
|
|
344
|
+
/** Get the action history / timeline for a bill. */
|
|
345
|
+
export async function getBillActions(congress, billType, billNumber, limit = 100) {
|
|
346
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/actions`, { limit });
|
|
347
|
+
return { actions: res.actions ?? [] };
|
|
348
|
+
}
|
|
349
|
+
/** Get amendments filed on a bill. */
|
|
350
|
+
export async function getBillAmendments(congress, billType, billNumber, limit = 50) {
|
|
351
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/amendments`, { limit });
|
|
352
|
+
return { amendments: res.amendments ?? [] };
|
|
353
|
+
}
|
|
354
|
+
/** Get committees a bill was referred to. */
|
|
355
|
+
export async function getBillCommittees(congress, billType, billNumber) {
|
|
356
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/committees`);
|
|
357
|
+
return { committees: res.committees ?? [] };
|
|
358
|
+
}
|
|
359
|
+
/** Get related/companion bills. */
|
|
360
|
+
export async function getBillRelatedBills(congress, billType, billNumber, limit = 50) {
|
|
361
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/relatedbills`, { limit });
|
|
362
|
+
return { relatedBills: res.relatedBills ?? [] };
|
|
363
|
+
}
|
|
364
|
+
/** Get legislative subjects tagged on a bill. */
|
|
365
|
+
export async function getBillSubjects(congress, billType, billNumber, limit = 100) {
|
|
366
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/subjects`, { limit });
|
|
367
|
+
return {
|
|
368
|
+
subjects: res.subjects?.legislativeSubjects ?? res.legislativeSubjects ?? [],
|
|
369
|
+
policyArea: res.subjects?.policyArea?.name ?? res.policyArea?.name,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/** Get CRS summaries of a bill. */
|
|
373
|
+
export async function getBillSummaries(congress, billType, billNumber) {
|
|
374
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/summaries`);
|
|
375
|
+
return { summaries: res.summaries ?? [] };
|
|
376
|
+
}
|
|
377
|
+
/** Get text versions available for a bill. */
|
|
378
|
+
export async function getBillTextVersions(congress, billType, billNumber) {
|
|
379
|
+
const res = await api.get(`/bill/${congress}/${billType.toLowerCase()}/${billNumber}/text`);
|
|
380
|
+
return { textVersions: res.textVersions ?? [] };
|
|
381
|
+
}
|
|
382
|
+
// ─── Member Details ──────────────────────────────────────────────────
|
|
383
|
+
/** Get detailed information about a specific member by bioguide ID. */
|
|
384
|
+
export async function getMemberDetails(bioguideId) {
|
|
385
|
+
const res = await api.get(`/member/${bioguideId}`);
|
|
386
|
+
return { member: res.member ?? res };
|
|
387
|
+
}
|
|
388
|
+
// ─── Amendments ──────────────────────────────────────────────────────
|
|
389
|
+
/** Search/list amendments. */
|
|
390
|
+
export async function searchAmendments(opts = {}) {
|
|
391
|
+
let path = "/amendment";
|
|
392
|
+
if (opts.congress) {
|
|
393
|
+
path += `/${opts.congress}`;
|
|
394
|
+
if (opts.amendmentType)
|
|
395
|
+
path += `/${opts.amendmentType.toLowerCase()}`;
|
|
396
|
+
}
|
|
397
|
+
const res = await api.get(path, {
|
|
398
|
+
limit: opts.limit ?? 20,
|
|
399
|
+
});
|
|
400
|
+
return { amendments: res.amendments ?? [] };
|
|
401
|
+
}
|
|
402
|
+
/** Get details about a specific amendment (includes actions). */
|
|
403
|
+
export async function getAmendmentDetails(congress, amendmentType, amendmentNumber) {
|
|
404
|
+
const amtType = amendmentType.toLowerCase();
|
|
405
|
+
const res = await api.get(`/amendment/${congress}/${amtType}/${amendmentNumber}`);
|
|
406
|
+
let actions = [];
|
|
407
|
+
try {
|
|
408
|
+
const aRes = await api.get(`/amendment/${congress}/${amtType}/${amendmentNumber}/actions`);
|
|
409
|
+
actions = aRes.actions ?? [];
|
|
410
|
+
}
|
|
411
|
+
catch { /* non-critical */ }
|
|
412
|
+
return {
|
|
413
|
+
amendment: res.amendment ?? res,
|
|
414
|
+
actions,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
// ─── Committees ──────────────────────────────────────────────────────
|
|
418
|
+
/** List congressional committees. */
|
|
419
|
+
export async function listCommittees(opts = {}) {
|
|
420
|
+
let path = "/committee";
|
|
421
|
+
if (opts.congress) {
|
|
422
|
+
path += `/${opts.congress}`;
|
|
423
|
+
}
|
|
424
|
+
if (opts.chamber) {
|
|
425
|
+
path += `/${opts.chamber.toLowerCase()}`;
|
|
426
|
+
}
|
|
427
|
+
const res = await api.get(path, {
|
|
428
|
+
limit: opts.limit ?? 50,
|
|
429
|
+
});
|
|
430
|
+
return { committees: res.committees ?? [] };
|
|
431
|
+
}
|
|
432
|
+
/** Get detailed committee info. */
|
|
433
|
+
export async function getCommitteeDetails(chamber, committeeCode) {
|
|
434
|
+
const res = await api.get(`/committee/${chamber.toLowerCase()}/${committeeCode}`);
|
|
435
|
+
return { committee: res.committee ?? res };
|
|
436
|
+
}
|
|
437
|
+
/** Get bills assigned to a committee. */
|
|
438
|
+
export async function getCommitteeBills(chamber, committeeCode, limit = 20) {
|
|
439
|
+
const res = await api.get(`/committee/${chamber.toLowerCase()}/${committeeCode}/bills`, { limit });
|
|
440
|
+
return { bills: res["committee-bills"]?.bills ?? res.bills ?? [] };
|
|
441
|
+
}
|
|
442
|
+
// ─── Nominations ─────────────────────────────────────────────────────
|
|
443
|
+
/** List presidential nominations for a congress. */
|
|
444
|
+
export async function listNominations(opts = {}) {
|
|
445
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
446
|
+
const res = await api.get(`/nomination/${congressNum}`, { limit: opts.limit ?? 20 });
|
|
447
|
+
return { nominations: res.nominations ?? [] };
|
|
448
|
+
}
|
|
449
|
+
/** Get details about a specific nomination (includes actions). */
|
|
450
|
+
export async function getNominationDetails(congress, nominationNumber) {
|
|
451
|
+
const res = await api.get(`/nomination/${congress}/${nominationNumber}`);
|
|
452
|
+
let actions = [];
|
|
453
|
+
try {
|
|
454
|
+
const aRes = await api.get(`/nomination/${congress}/${nominationNumber}/actions`);
|
|
455
|
+
actions = aRes.actions ?? [];
|
|
456
|
+
}
|
|
457
|
+
catch { /* non-critical */ }
|
|
458
|
+
return {
|
|
459
|
+
nomination: res.nomination ?? res,
|
|
460
|
+
actions,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// ─── Treaties ────────────────────────────────────────────────────────
|
|
464
|
+
/** List treaties. */
|
|
465
|
+
export async function listTreaties(opts = {}) {
|
|
466
|
+
let path = "/treaty";
|
|
467
|
+
if (opts.congress)
|
|
468
|
+
path += `/${opts.congress}`;
|
|
469
|
+
const res = await api.get(path, {
|
|
470
|
+
limit: opts.limit ?? 20,
|
|
471
|
+
});
|
|
472
|
+
return { treaties: res.treaties ?? [] };
|
|
473
|
+
}
|
|
474
|
+
/** Get details about a specific treaty (includes actions). */
|
|
475
|
+
export async function getTreatyDetails(congress, treatyNumber) {
|
|
476
|
+
const res = await api.get(`/treaty/${congress}/${treatyNumber}`);
|
|
477
|
+
let actions = [];
|
|
478
|
+
try {
|
|
479
|
+
const aRes = await api.get(`/treaty/${congress}/${treatyNumber}/actions`);
|
|
480
|
+
actions = aRes.actions ?? [];
|
|
481
|
+
}
|
|
482
|
+
catch { /* non-critical */ }
|
|
483
|
+
return {
|
|
484
|
+
treaty: res.treaty ?? res,
|
|
485
|
+
actions,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// ─── CRS Reports ─────────────────────────────────────────────────────
|
|
489
|
+
/** Search Congressional Research Service reports. */
|
|
490
|
+
export async function searchCrsReports(opts = {}) {
|
|
491
|
+
const res = await api.get("/crsreport", { limit: opts.limit ?? 20 });
|
|
492
|
+
return { reports: res.CRSReports ?? res.reports ?? [] };
|
|
493
|
+
}
|
|
494
|
+
/** Get a specific CRS report by report number. */
|
|
495
|
+
export async function getCrsReportDetails(reportNumber) {
|
|
496
|
+
const res = await api.get(`/crsreport/${reportNumber}`);
|
|
497
|
+
return { report: res.CRSReport ?? res.report ?? res };
|
|
498
|
+
}
|
|
499
|
+
// ─── Congressional Record ────────────────────────────────────────────
|
|
500
|
+
/** Get Congressional Record issues. */
|
|
501
|
+
export async function getCongressionalRecord(opts = {}) {
|
|
502
|
+
const params = { limit: opts.limit ?? 20 };
|
|
503
|
+
if (opts.year)
|
|
504
|
+
params.y = opts.year;
|
|
505
|
+
if (opts.month)
|
|
506
|
+
params.m = opts.month;
|
|
507
|
+
if (opts.day)
|
|
508
|
+
params.d = opts.day;
|
|
509
|
+
const res = await api.get("/congressional-record", params);
|
|
510
|
+
return { issues: res.Results?.Issues ?? res.congressionalRecord ?? res.issues ?? [] };
|
|
511
|
+
}
|
|
512
|
+
// ─── Roll Call Votes (from clerk.house.gov & senate.gov XML) ─────────
|
|
513
|
+
import { XMLParser } from "fast-xml-parser";
|
|
514
|
+
const xmlParser = new XMLParser({
|
|
515
|
+
ignoreAttributes: false,
|
|
516
|
+
trimValues: true,
|
|
517
|
+
// Ensure arrays for repeating elements in House and Senate vote XML
|
|
518
|
+
isArray: (name) => name === "member" || name === "vote" ||
|
|
519
|
+
name === "recorded-vote" || name === "totals-by-party",
|
|
520
|
+
// Parse numeric-looking values as numbers
|
|
521
|
+
parseTagValue: true,
|
|
522
|
+
});
|
|
523
|
+
/** Parse XML string into a JS object using fast-xml-parser. */
|
|
524
|
+
function parseXml(xml) {
|
|
525
|
+
return xmlParser.parse(xml);
|
|
526
|
+
}
|
|
527
|
+
const HOUSE_CLERK_BASE = "https://clerk.house.gov/evs";
|
|
528
|
+
const SENATE_BASE = "https://www.senate.gov/legislative/LIS";
|
|
529
|
+
function padVoteNumber(n) {
|
|
530
|
+
return String(n).padStart(5, "0");
|
|
531
|
+
}
|
|
532
|
+
/** Convert congress number + session to calendar year. */
|
|
533
|
+
function congressSessionToYear(congress, session) {
|
|
534
|
+
return (congress - 1) * 2 + 1788 + session;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get Senate roll call votes from senate.gov XML data.
|
|
538
|
+
*
|
|
539
|
+
* Coverage: 101st Congress (1989) to present — far deeper than the Congress.gov API
|
|
540
|
+
* which only has House votes for 118th-119th Congress.
|
|
541
|
+
*
|
|
542
|
+
* Data source: https://www.senate.gov/general/XML.htm
|
|
543
|
+
*/
|
|
544
|
+
export async function getSenateVotes(opts = {}) {
|
|
545
|
+
const congressNum = opts.congress ?? currentCongress();
|
|
546
|
+
const sessionNum = opts.session ?? currentSession();
|
|
547
|
+
if (opts.vote_number) {
|
|
548
|
+
// Fetch individual vote XML
|
|
549
|
+
const url = `${SENATE_BASE}/roll_call_votes/vote${congressNum}${sessionNum}` +
|
|
550
|
+
`/vote_${congressNum}_${sessionNum}_${padVoteNumber(opts.vote_number)}.xml`;
|
|
551
|
+
const resp = await fetch(url, {
|
|
552
|
+
headers: { "User-Agent": "us-gov-open-data-mcp/2.0 (gov-accountability-tool)" },
|
|
553
|
+
signal: AbortSignal.timeout(30_000),
|
|
554
|
+
});
|
|
555
|
+
if (!resp.ok)
|
|
556
|
+
throw new Error(`Senate vote fetch failed: ${resp.status} ${resp.statusText} (${url})`);
|
|
557
|
+
const xml = await resp.text();
|
|
558
|
+
const parsed = parseXml(xml);
|
|
559
|
+
const rc = parsed.roll_call_vote ?? {};
|
|
560
|
+
// Parse vote metadata
|
|
561
|
+
const doc = rc.document;
|
|
562
|
+
const cnt = (rc.count ?? {});
|
|
563
|
+
const tb = (rc.tie_breaker ?? {});
|
|
564
|
+
const vote = {
|
|
565
|
+
congress: Number(rc.congress) || congressNum,
|
|
566
|
+
session: Number(rc.session) || sessionNum,
|
|
567
|
+
voteNumber: Number(rc.vote_number) || opts.vote_number,
|
|
568
|
+
date: String(rc.vote_date ?? ""),
|
|
569
|
+
question: String(rc.question ?? ""),
|
|
570
|
+
result: String(rc.vote_result ?? ""),
|
|
571
|
+
title: String(rc.vote_title ?? ""),
|
|
572
|
+
description: String(rc.vote_document_text ?? ""),
|
|
573
|
+
majorityRequired: String(rc.majority_requirement ?? ""),
|
|
574
|
+
count: {
|
|
575
|
+
yeas: Number(cnt.yeas) || 0,
|
|
576
|
+
nays: Number(cnt.nays) || 0,
|
|
577
|
+
present: Number(cnt.present) || 0,
|
|
578
|
+
absent: Number(cnt.absent) || 0,
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
if (doc) {
|
|
582
|
+
vote.document = {
|
|
583
|
+
type: String(doc.document_type ?? ""),
|
|
584
|
+
number: String(doc.document_number ?? ""),
|
|
585
|
+
name: String(doc.document_name ?? ""),
|
|
586
|
+
title: String(doc.document_title ?? ""),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const tbWho = String(tb.by_whom ?? "");
|
|
590
|
+
if (tbWho) {
|
|
591
|
+
vote.tieBreaker = { byWhom: tbWho, vote: String(tb.tie_breaker_vote ?? "") };
|
|
592
|
+
}
|
|
593
|
+
// Parse members
|
|
594
|
+
const membersContainer = (rc.members ?? {});
|
|
595
|
+
const rawMembers = (membersContainer.member ?? []);
|
|
596
|
+
const members = rawMembers.map((m) => ({
|
|
597
|
+
fullName: String(m.member_full ?? ""),
|
|
598
|
+
firstName: String(m.first_name ?? ""),
|
|
599
|
+
lastName: String(m.last_name ?? ""),
|
|
600
|
+
party: String(m.party ?? ""),
|
|
601
|
+
state: String(m.state ?? ""),
|
|
602
|
+
voteCast: String(m.vote_cast ?? ""),
|
|
603
|
+
}));
|
|
604
|
+
// Build party tally
|
|
605
|
+
const partyTally = {};
|
|
606
|
+
for (const m of members) {
|
|
607
|
+
const p = m.party || "?";
|
|
608
|
+
const v = m.voteCast || "?";
|
|
609
|
+
if (!partyTally[p])
|
|
610
|
+
partyTally[p] = {};
|
|
611
|
+
partyTally[p][v] = (partyTally[p][v] ?? 0) + 1;
|
|
612
|
+
}
|
|
613
|
+
return { vote, members, partyTally };
|
|
614
|
+
}
|
|
615
|
+
// List recent votes — fetch list XML
|
|
616
|
+
const listUrl = `${SENATE_BASE}/roll_call_lists/vote_menu_${congressNum}_${sessionNum}.xml`;
|
|
617
|
+
const resp = await fetch(listUrl, {
|
|
618
|
+
headers: { "User-Agent": "us-gov-open-data-mcp/2.0 (gov-accountability-tool)" },
|
|
619
|
+
signal: AbortSignal.timeout(30_000),
|
|
620
|
+
});
|
|
621
|
+
if (!resp.ok)
|
|
622
|
+
throw new Error(`Senate vote list fetch failed: ${resp.status} ${resp.statusText} (${listUrl})`);
|
|
623
|
+
const xml = await resp.text();
|
|
624
|
+
const parsed = parseXml(xml);
|
|
625
|
+
const summary = parsed.vote_summary ?? {};
|
|
626
|
+
const rawVotes = (summary.votes ?? {}).vote ?? [];
|
|
627
|
+
const voteArr = (Array.isArray(rawVotes) ? rawVotes : [rawVotes]);
|
|
628
|
+
const maxResults = opts.limit ?? 20;
|
|
629
|
+
const votes = voteArr.slice(0, maxResults).map((v) => {
|
|
630
|
+
const tally = (v.vote_tally ?? {});
|
|
631
|
+
return {
|
|
632
|
+
congress: congressNum,
|
|
633
|
+
session: sessionNum,
|
|
634
|
+
voteNumber: Number(v.vote_number) || 0,
|
|
635
|
+
date: String(v.vote_date ?? ""),
|
|
636
|
+
question: String(v.question ?? ""),
|
|
637
|
+
result: String(v.result ?? ""),
|
|
638
|
+
title: String(v.title ?? ""),
|
|
639
|
+
description: String(v.issue ?? ""),
|
|
640
|
+
majorityRequired: "",
|
|
641
|
+
count: {
|
|
642
|
+
yeas: Number(tally.yeas) || 0,
|
|
643
|
+
nays: Number(tally.nays) || 0,
|
|
644
|
+
present: 0,
|
|
645
|
+
absent: 0,
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
return { votes };
|
|
650
|
+
}
|
|
651
|
+
/** Helper: current session (1 for odd years, 2 for even years). */
|
|
652
|
+
function currentSession() {
|
|
653
|
+
return new Date().getFullYear() % 2 === 1 ? 1 : 2;
|
|
654
|
+
}
|
|
655
|
+
/** Clear cached responses. */
|
|
656
|
+
export function clearCache() {
|
|
657
|
+
api.clearCache();
|
|
658
|
+
}
|
|
659
|
+
//# sourceMappingURL=congress.js.map
|