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
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# US Government Open Data MCP
|
|
2
|
+
|
|
3
|
+
An MCP server + TypeScript SDK for **36 U.S. government and international data APIs** — 188 tools covering economic, fiscal, health, education, energy, environment, lobbying, housing, patents, safety, banking, consumer protection, workplace safety, transportation, seismic, clinical trials, and legislative data.
|
|
4
|
+
|
|
5
|
+
Works with any MCP client (VS Code Copilot, Claude Desktop, Cursor) via **stdio or HTTP Stream**. Supports **selective module loading** and standalone **SDK imports** for use without MCP.
|
|
6
|
+
|
|
7
|
+
Built with [FastMCP](https://github.com/punkpeye/fastmcp). Disk-cached, rate-limited, retries with backoff.
|
|
8
|
+
|
|
9
|
+
## Data Sources
|
|
10
|
+
|
|
11
|
+
| API | What it covers | Auth |
|
|
12
|
+
|-----|---------------|------|
|
|
13
|
+
| **Treasury Fiscal Data** | 53 datasets, 181 endpoints: national debt, revenue, spending, interest rates, exchange rates, gold reserves | None |
|
|
14
|
+
| **FRED** | 800K+ economic time series: GDP, CPI, unemployment, interest rates, money supply, housing, S&P 500 | `FRED_API_KEY` |
|
|
15
|
+
| **BLS** | Employment by industry, wages, CPI by component, PPI, JOLTS, labor productivity | `BLS_API_KEY` (optional) |
|
|
16
|
+
| **BEA** | State GDP, GDP by industry, personal income by state, national accounts (NIPA) | `BEA_API_KEY` |
|
|
17
|
+
| **EIA** | Petroleum, electricity, natural gas prices; state energy profiles | `EIA_API_KEY` |
|
|
18
|
+
| **Census Bureau** | Population, demographics, income, housing from ACS and Decennial Census | `CENSUS_API_KEY` |
|
|
19
|
+
| **OpenFEC** | Campaign finance: candidates, committees, contributions, expenditures | `DATA_GOV_API_KEY` |
|
|
20
|
+
| **Congress.gov** | Bills, members, laws, amendments, House & Senate roll call votes (1990+) | `DATA_GOV_API_KEY` |
|
|
21
|
+
| **Federal Register** | Executive orders, presidential documents, rules, agency notices, document detail, 470+ agency directory | None |
|
|
22
|
+
| **USAspending** | Federal contracts, grants, loans — $6T+ annually by agency, recipient, state | None |
|
|
23
|
+
| **SEC EDGAR** | Company filings, XBRL financials, full-text search | `SEC_CONTACT_EMAIL` |
|
|
24
|
+
| **FBI Crime Data** | Crime statistics by state, national estimates, arrests | `DATA_GOV_API_KEY` |
|
|
25
|
+
| **GovInfo** | Full text of bills, laws, CBO reports, Federal Register | `DATA_GOV_API_KEY` |
|
|
26
|
+
| **NOAA Climate** | Weather observations, temperature, precipitation from U.S. stations | `NOAA_API_KEY` |
|
|
27
|
+
| **USDA NASS** | Crop production, livestock, farm prices, Census of Agriculture | `USDA_NASS_API_KEY` |
|
|
28
|
+
| **World Bank** | International indicators: GDP, population, health spending for 200+ countries | None |
|
|
29
|
+
| **CDC Health Data** | Leading causes of death, life expectancy, mortality rates, county/city health indicators (PLACES), drug overdose, obesity, disability, birth indicators, weekly death surveillance, COVID-19 — 13 tools across 12 datasets | None |
|
|
30
|
+
| **NAEP (Nation's Report Card)** | Reading, math, science test scores by state, grade, race, gender, poverty — the gold standard for U.S. education measurement | None |
|
|
31
|
+
| **College Scorecard** | College costs, graduation rates, post-graduation earnings, student debt, admission rates for every U.S. college | `DATA_GOV_API_KEY` |
|
|
32
|
+
| **NREL (Clean Energy)** | EV charging stations, alt fuel infrastructure, electricity rates, solar resource data | `DATA_GOV_API_KEY` |
|
|
33
|
+
| **FDA (OpenFDA)** | Drug adverse events (20M+ reports), drug recalls, FDA-approved drugs (Drugs@FDA), drug labels, food recalls, food adverse events (CAERS), medical device events, device recalls — 8 tools | None |
|
|
34
|
+
| **EPA** | Air quality data, facility compliance/violations, UV index forecasts | None |
|
|
35
|
+
| **Senate Lobbying Disclosures** | Lobbying filings, expenditures by issue, campaign contributions, individual lobbyist search — follow the money | None |
|
|
36
|
+
| **Regulations.gov** | Federal rulemaking: proposed rules, final rules, public comments, regulatory dockets | `DATA_GOV_API_KEY` |
|
|
37
|
+
| **USDA FoodData Central** | Nutritional data for 300K+ foods: calories, macros, vitamins, minerals for branded and reference foods | `DATA_GOV_API_KEY` |
|
|
38
|
+
| **FEMA** | Disaster declarations, housing assistance, public assistance, NFIP flood claims, hazard mitigation | None |
|
|
39
|
+
| **NHTSA** | Vehicle safety recalls, consumer complaints, NCAP 5-star safety ratings, VIN decoding, make/model lookup | None |
|
|
40
|
+
| **CMS Provider Data** | Hospital quality ratings, nursing home inspections, home health, hospice, dialysis facility data | None |
|
|
41
|
+
| **HUD** | Fair Market Rents by bedroom count, income limits by household size, county/metro area housing data | `HUD_USER_TOKEN` |
|
|
42
|
+
| **USPTO PatentsView** | U.S. patent search by keyword, assignee, inventor, CPC class; inventor and assignee lookup | None |
|
|
43
|
+
| **CFPB** | Consumer complaint database: 13M+ complaints against financial companies, searchable by company/product/state/issue with trend analysis | None |
|
|
44
|
+
| **FDIC** | Bank data: 5,000+ insured institutions, failures since 1934, quarterly financials, branch deposits, merger history | None |
|
|
45
|
+
| **DOL** | OSHA inspections/violations/accidents, WHD wage theft enforcement, weekly unemployment insurance claims (national + state) | `DOL_API_KEY` |
|
|
46
|
+
| **USGS** | Earthquake events (magnitude, location, depth, tsunami risk), water resources monitoring (real-time and daily historical streamflow, water levels) from 13,000+ stations | None |
|
|
47
|
+
| **ClinicalTrials.gov** | 400K+ clinical trials: search by condition, drug, sponsor, phase, status, location. Track pharma drug pipelines | None |
|
|
48
|
+
| **BTS** | Bureau of Transportation Statistics: monthly transport stats (airline traffic, transit, rail, safety, fuel), border crossings at U.S. ports of entry | None |
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx us-gov-open-data-mcp
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### VS Code / Copilot
|
|
57
|
+
|
|
58
|
+
Add to `.vscode/mcp.json`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"servers": {
|
|
63
|
+
"us-gov-open-data": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "us-gov-open-data-mcp"],
|
|
66
|
+
"env": {
|
|
67
|
+
"FRED_API_KEY": "your_key",
|
|
68
|
+
"DATA_GOV_API_KEY": "your_key"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Claude Desktop
|
|
76
|
+
|
|
77
|
+
Add to `claude_desktop_config.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"us-gov-open-data": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["-y", "us-gov-open-data-mcp"],
|
|
85
|
+
"env": {
|
|
86
|
+
"FRED_API_KEY": "your_key"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### HTTP Stream (for web apps, remote access)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Start on port 8080
|
|
97
|
+
node dist/server.js --transport httpStream --port 8080
|
|
98
|
+
|
|
99
|
+
# Or via npm script
|
|
100
|
+
npm run start:http
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The HTTP Stream endpoint will be at `http://localhost:8080/mcp`. Works with any MCP client that supports HTTP transport.
|
|
104
|
+
|
|
105
|
+
### Selective Module Loading
|
|
106
|
+
|
|
107
|
+
Load only the modules you need for faster startup and smaller tool lists:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Via CLI flag
|
|
111
|
+
node dist/server.js --modules fred,treasury,congress
|
|
112
|
+
|
|
113
|
+
# Via environment variable
|
|
114
|
+
MODULES=fred,bls,treasury node dist/server.js
|
|
115
|
+
|
|
116
|
+
# Combine with HTTP transport
|
|
117
|
+
node dist/server.js --modules fred,treasury --transport httpStream --port 8080
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Available module names: `treasury`, `fred`, `bls`, `bea`, `eia`, `census`, `fec`, `congress`, `federal-register`, `usaspending`, `sec`, `fbi`, `govinfo`, `noaa`, `usda-nass`, `world-bank`, `cdc`, `naep`, `college-scorecard`, `nrel`, `fda`, `epa`, `senate-lobbying`, `regulations`, `usda-fooddata`, `fema`, `nhtsa`, `cms`, `hud`, `uspto`, `cfpb`, `fdic`, `dol`, `usgs`, `clinical-trials`, `bts`
|
|
121
|
+
|
|
122
|
+
## API Keys
|
|
123
|
+
|
|
124
|
+
18 APIs require **no key at all** (Treasury, Federal Register, USAspending, World Bank, CDC, FDA, EPA, NAEP, Senate Lobbying, FEMA, NHTSA, CMS, USPTO, CFPB, FDIC, USGS, ClinicalTrials.gov, BTS). The rest need free keys — most take under a minute to get:
|
|
125
|
+
|
|
126
|
+
| Key | Where to get it | Used by |
|
|
127
|
+
|-----|----------------|---------|
|
|
128
|
+
| `DATA_GOV_API_KEY` | [api.data.gov/signup](https://api.data.gov/signup/) | Congress, FEC, FBI, GovInfo, College Scorecard, NREL, Regulations.gov, USDA FoodData |
|
|
129
|
+
| `FRED_API_KEY` | [fredaccount.stlouisfed.org/apikeys](https://fredaccount.stlouisfed.org/apikeys) | FRED |
|
|
130
|
+
| `CENSUS_API_KEY` | [api.census.gov/data/key_signup.html](https://api.census.gov/data/key_signup.html) | Census |
|
|
131
|
+
| `BLS_API_KEY` | [bls.gov/developers](https://www.bls.gov/developers/) | BLS (optional — works without, higher limits with) |
|
|
132
|
+
| `BEA_API_KEY` | [apps.bea.gov/API/signup](https://apps.bea.gov/API/signup/) | BEA |
|
|
133
|
+
| `EIA_API_KEY` | [eia.gov/opendata/register.php](https://www.eia.gov/opendata/register.php) | EIA |
|
|
134
|
+
| `NOAA_API_KEY` | [ncei.noaa.gov/cdo-web/token](https://www.ncei.noaa.gov/cdo-web/token) | NOAA |
|
|
135
|
+
| `USDA_NASS_API_KEY` | [quickstats.nass.usda.gov/api](https://quickstats.nass.usda.gov/api) | USDA NASS |
|
|
136
|
+
| `HUD_USER_TOKEN` | [huduser.gov/hudapi/public/register](https://www.huduser.gov/hudapi/public/register) | HUD |
|
|
137
|
+
| `SEC_CONTACT_EMAIL` | Any valid email | SEC EDGAR |
|
|
138
|
+
| `DOL_API_KEY` | [data.dol.gov/registration](https://data.dol.gov/registration) | DOL (OSHA, WHD, UI Claims) |
|
|
139
|
+
|
|
140
|
+
Set keys via environment variables or a `.env` file in the project root.
|
|
141
|
+
|
|
142
|
+
## Using as a TypeScript SDK
|
|
143
|
+
|
|
144
|
+
Every API is importable as a standalone typed client — no MCP server required:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// Individual module imports
|
|
148
|
+
import { getObservations } from "us-gov-open-data-mcp/sdk/fred";
|
|
149
|
+
import { getIndicator } from "us-gov-open-data-mcp/sdk/world-bank";
|
|
150
|
+
import { getLeadingCausesOfDeath } from "us-gov-open-data-mcp/sdk/cdc";
|
|
151
|
+
import { searchBills } from "us-gov-open-data-mcp/sdk/congress";
|
|
152
|
+
|
|
153
|
+
// Or barrel import everything
|
|
154
|
+
import * as sdk from "us-gov-open-data-mcp/sdk";
|
|
155
|
+
const gdp = await sdk.fred.getObservations("GDP", { sort: "desc", limit: 5 });
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// FRED
|
|
160
|
+
const gdp = await getObservations("GDP", { sort: "desc", limit: 5 });
|
|
161
|
+
console.log(gdp.observations); // [{ date: "2025-10-01", value: "31490.07" }, ...]
|
|
162
|
+
|
|
163
|
+
// World Bank — compare U.S. to Germany
|
|
164
|
+
const health = await getIndicator("SH.XPD.CHEX.PC.CD", { country: "US;DE", dateRange: "2015:2023" });
|
|
165
|
+
|
|
166
|
+
// CDC
|
|
167
|
+
const deaths = await getLeadingCausesOfDeath({ state: "California", year: 2017 });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
All SDK functions include disk-backed caching, retry with exponential backoff, and token-bucket rate limiting. See [docs/sdk.md](docs/sdk.md) for the full API reference.
|
|
171
|
+
|
|
172
|
+
## Architecture
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
src/
|
|
176
|
+
client.ts # createClient() factory — cache, retry, rate-limit
|
|
177
|
+
server.ts # FastMCP bootstrap — stdio + HTTP, selective loading
|
|
178
|
+
instructions.ts # Cross-referencing guide for MCP clients
|
|
179
|
+
sdk/
|
|
180
|
+
index.ts # Barrel export for all SDK modules
|
|
181
|
+
fred.ts, bls.ts, ... # Typed API clients (no MCP dependency)
|
|
182
|
+
modules/
|
|
183
|
+
fred.ts, bls.ts, ... # MCP tool definitions + metadata
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Each API is 2 files:
|
|
187
|
+
- **`sdk/*.ts`** — typed async functions, usable anywhere (no MCP/Zod dependency)
|
|
188
|
+
- **`modules/*.ts`** — MCP tool definitions wrapping the SDK, with metadata for client instructions
|
|
189
|
+
|
|
190
|
+
Adding a new API: create `sdk/new-api.ts` + `modules/new-api.ts`, add 2 lines to `server.ts`. See [docs/adding-modules.md](docs/adding-modules.md).
|
|
191
|
+
|
|
192
|
+
## Documentation
|
|
193
|
+
|
|
194
|
+
- [SDK API Reference](docs/sdk.md) — all exported functions and types
|
|
195
|
+
- [Adding New Modules](docs/adding-modules.md) — how to add a new API
|
|
196
|
+
- [Architecture](docs/architecture.md) — how the system works
|
|
197
|
+
|
|
198
|
+
## Disclaimer
|
|
199
|
+
|
|
200
|
+
This project was built with the assistance of AI tools. All data is sourced from official U.S. government and international APIs — the server does not generate, modify, or editorialize any data. It returns raw results from federal databases exactly as provided.
|
|
201
|
+
|
|
202
|
+
**Important:**
|
|
203
|
+
- Data accuracy depends on the upstream government APIs. Numbers may lag days to years behind reality depending on the source.
|
|
204
|
+
- Correlation does not imply causation. Cross-referencing data sources (e.g., campaign finance + legislative votes) shows documented patterns, not proven cause-and-effect.
|
|
205
|
+
- This tool is for research and informational purposes. It is not legal, financial, medical, or policy advice.
|
|
206
|
+
- API rate limits and availability vary. Some endpoints may be temporarily unavailable or return incomplete data.
|
|
207
|
+
- The example analyses in the `examples/` folder demonstrate the server's capabilities and should not be treated as investigative conclusions.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight API client factory with caching, retry, and rate limiting.
|
|
3
|
+
*
|
|
4
|
+
* Instead of an abstract class with 7 virtual methods, this uses a config
|
|
5
|
+
* object to create a client. Each module calls createClient() once.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Disk-backed TTL cache (survives MCP server restarts)
|
|
9
|
+
* - Timeout (30s default)
|
|
10
|
+
* - Retry with exponential backoff (429, 502, 503, 504)
|
|
11
|
+
* - Token-bucket rate limiting
|
|
12
|
+
* - Auth via query param, header, or request body
|
|
13
|
+
*/
|
|
14
|
+
export interface ClientConfig {
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
name: string;
|
|
17
|
+
/** Auth configuration — how to attach the API key to requests */
|
|
18
|
+
auth?: {
|
|
19
|
+
/** Where to inject the key */
|
|
20
|
+
type: "query" | "header" | "body";
|
|
21
|
+
/** The param/header name (e.g. "api_key", "Authorization", "registrationkey") */
|
|
22
|
+
key: string;
|
|
23
|
+
/** Env var to read the key from */
|
|
24
|
+
envVar: string;
|
|
25
|
+
/** Extra static params (e.g. { file_type: "json" } for FRED) */
|
|
26
|
+
extraParams?: Record<string, string>;
|
|
27
|
+
/** For header auth: prefix like "Bearer " */
|
|
28
|
+
prefix?: string;
|
|
29
|
+
};
|
|
30
|
+
/** Rate limiting */
|
|
31
|
+
rateLimit?: {
|
|
32
|
+
perSecond: number;
|
|
33
|
+
burst: number;
|
|
34
|
+
};
|
|
35
|
+
/** Default headers on every request (e.g. User-Agent for SEC) */
|
|
36
|
+
defaultHeaders?: Record<string, string>;
|
|
37
|
+
/** Cache TTL in ms (default: 5 min). Government data often updates daily/weekly — set
|
|
38
|
+
* higher for infrequent data: 1 hour = 3_600_000, 1 day = 86_400_000. Set 0 to disable. */
|
|
39
|
+
cacheTtlMs?: number;
|
|
40
|
+
/** Timeout in ms (default: 30000) */
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
/** Custom error detector — some APIs return 200 OK with errors in the body */
|
|
43
|
+
checkError?: (data: unknown) => string | null;
|
|
44
|
+
}
|
|
45
|
+
/** Param values: string, number, string[] (for repeated keys like facets[series][]), or undefined to skip */
|
|
46
|
+
export type ParamValue = string | number | string[] | undefined;
|
|
47
|
+
export type Params = Record<string, ParamValue>;
|
|
48
|
+
export interface ApiClient {
|
|
49
|
+
get<T = unknown>(path: string, params?: Params): Promise<T>;
|
|
50
|
+
post<T = unknown>(path: string, body?: Record<string, unknown>, params?: Params): Promise<T>;
|
|
51
|
+
clearCache(): void;
|
|
52
|
+
}
|
|
53
|
+
export declare function createClient(config: ClientConfig): ApiClient;
|
|
54
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,iEAAiE;IACjE,IAAI,CAAC,EAAE;QACL,8BAA8B;QAC9B,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;QAClC,iFAAiF;QACjF,GAAG,EAAE,MAAM,CAAC;QACZ,mCAAmC;QACnC,MAAM,EAAE,MAAM,CAAC;QACf,gEAAgE;QAChE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,6CAA6C;QAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,oBAAoB;IACpB,SAAS,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAEjD,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;gGAC4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;CAC/C;AAED,6GAA6G;AAC7G,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7F,UAAU,IAAI,IAAI,CAAC;CACpB;AA8RD,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CA0H5D"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight API client factory with caching, retry, and rate limiting.
|
|
3
|
+
*
|
|
4
|
+
* Instead of an abstract class with 7 virtual methods, this uses a config
|
|
5
|
+
* object to create a client. Each module calls createClient() once.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Disk-backed TTL cache (survives MCP server restarts)
|
|
9
|
+
* - Timeout (30s default)
|
|
10
|
+
* - Retry with exponential backoff (429, 502, 503, 504)
|
|
11
|
+
* - Token-bucket rate limiting
|
|
12
|
+
* - Auth via query param, header, or request body
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
15
|
+
import { writeFile } from "node:fs/promises";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { homedir, tmpdir } from "node:os";
|
|
18
|
+
// ─── Token Bucket Rate Limiter ───────────────────────────────────────
|
|
19
|
+
class TokenBucket {
|
|
20
|
+
max;
|
|
21
|
+
rate;
|
|
22
|
+
tokens;
|
|
23
|
+
lastRefill;
|
|
24
|
+
constructor(max, rate) {
|
|
25
|
+
this.max = max;
|
|
26
|
+
this.rate = rate;
|
|
27
|
+
this.tokens = max;
|
|
28
|
+
this.lastRefill = Date.now();
|
|
29
|
+
}
|
|
30
|
+
async acquire() {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
this.tokens = Math.min(this.max, this.tokens + ((now - this.lastRefill) / 1000) * this.rate);
|
|
33
|
+
this.lastRefill = now;
|
|
34
|
+
if (this.tokens >= 1) {
|
|
35
|
+
this.tokens -= 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const waitMs = Math.ceil(((1 - this.tokens) / this.rate) * 1000);
|
|
39
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
40
|
+
this.tokens = 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ─── Disk-backed TTL Cache ────────────────────────────────────────────
|
|
44
|
+
//
|
|
45
|
+
// Single consolidated JSON file shared by all modules. Lazy-loaded on
|
|
46
|
+
// first cache miss. LRU eviction per module keeps memory bounded.
|
|
47
|
+
// Async writes don't block the event loop. Global write coalescing
|
|
48
|
+
// batches all module updates into one disk write.
|
|
49
|
+
function getCacheDir() {
|
|
50
|
+
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
51
|
+
const dir = join(base, "us-gov-open-data-mcp");
|
|
52
|
+
try {
|
|
53
|
+
if (!existsSync(dir))
|
|
54
|
+
mkdirSync(dir, { recursive: true });
|
|
55
|
+
return dir;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
const fallback = join(tmpdir(), "us-gov-open-data-mcp");
|
|
59
|
+
if (!existsSync(fallback))
|
|
60
|
+
mkdirSync(fallback, { recursive: true });
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const CACHE_DIR = getCacheDir();
|
|
65
|
+
const CACHE_FILE = join(CACHE_DIR, "cache.json");
|
|
66
|
+
const MAX_ENTRIES_PER_MODULE = 200;
|
|
67
|
+
// ─── Global disk store (shared by all DiskCache instances) ───────────
|
|
68
|
+
let _globalLoaded = false;
|
|
69
|
+
let _globalDirty = false;
|
|
70
|
+
let _globalWriteTimer;
|
|
71
|
+
/** namespace → key → entry */
|
|
72
|
+
const _globalStore = new Map();
|
|
73
|
+
function loadGlobal() {
|
|
74
|
+
if (_globalLoaded)
|
|
75
|
+
return;
|
|
76
|
+
_globalLoaded = true;
|
|
77
|
+
try {
|
|
78
|
+
if (!existsSync(CACHE_FILE)) {
|
|
79
|
+
// Migrate: try loading legacy per-module files
|
|
80
|
+
migrateLegacyFiles();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const raw = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
let totalLoaded = 0;
|
|
86
|
+
for (const [ns, entries] of Object.entries(raw)) {
|
|
87
|
+
const map = new Map();
|
|
88
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
89
|
+
if (entry.expires > now) {
|
|
90
|
+
map.set(key, entry);
|
|
91
|
+
totalLoaded++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (map.size > 0)
|
|
95
|
+
_globalStore.set(ns, map);
|
|
96
|
+
}
|
|
97
|
+
if (totalLoaded > 0 && process.env.DEBUG_CACHE) {
|
|
98
|
+
console.error(`Cache: loaded ${totalLoaded} entries from disk (${_globalStore.size} modules)`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Corrupted — start fresh
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** One-time migration from the old per-module *.json files to the consolidated cache.json */
|
|
106
|
+
function migrateLegacyFiles() {
|
|
107
|
+
try {
|
|
108
|
+
const { readdirSync, unlinkSync } = require("node:fs");
|
|
109
|
+
const files = readdirSync(CACHE_DIR).filter((f) => f.endsWith(".json") && f !== "cache.json");
|
|
110
|
+
if (files.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
let migrated = 0;
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
try {
|
|
116
|
+
const ns = file.replace(/\.json$/, "");
|
|
117
|
+
const raw = JSON.parse(readFileSync(join(CACHE_DIR, file), "utf-8"));
|
|
118
|
+
const map = new Map();
|
|
119
|
+
for (const [key, entry] of Object.entries(raw)) {
|
|
120
|
+
if (entry.expires > now) {
|
|
121
|
+
// Add lastAccess if missing (legacy entries don't have it)
|
|
122
|
+
if (!entry.lastAccess)
|
|
123
|
+
entry.lastAccess = now;
|
|
124
|
+
map.set(key, entry);
|
|
125
|
+
migrated++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (map.size > 0)
|
|
129
|
+
_globalStore.set(ns, map);
|
|
130
|
+
unlinkSync(join(CACHE_DIR, file)); // Remove legacy file
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Skip corrupt file
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (migrated > 0) {
|
|
137
|
+
_globalDirty = true;
|
|
138
|
+
scheduleGlobalWrite();
|
|
139
|
+
if (process.env.DEBUG_CACHE) {
|
|
140
|
+
console.error(`Cache: migrated ${migrated} entries from ${files.length} legacy files`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Migration is best-effort
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function scheduleGlobalWrite() {
|
|
149
|
+
if (_globalWriteTimer)
|
|
150
|
+
return;
|
|
151
|
+
_globalWriteTimer = setTimeout(() => {
|
|
152
|
+
_globalWriteTimer = undefined;
|
|
153
|
+
if (!_globalDirty)
|
|
154
|
+
return;
|
|
155
|
+
_globalDirty = false;
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const obj = {};
|
|
158
|
+
for (const [ns, map] of _globalStore) {
|
|
159
|
+
const entries = {};
|
|
160
|
+
for (const [key, entry] of map) {
|
|
161
|
+
if (entry.expires > now)
|
|
162
|
+
entries[key] = entry;
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(entries).length > 0)
|
|
165
|
+
obj[ns] = entries;
|
|
166
|
+
}
|
|
167
|
+
// Async write — non-blocking
|
|
168
|
+
writeFile(CACHE_FILE, JSON.stringify(obj), "utf-8").catch(() => { });
|
|
169
|
+
}, 2000);
|
|
170
|
+
if (typeof _globalWriteTimer === "object" && "unref" in _globalWriteTimer) {
|
|
171
|
+
_globalWriteTimer.unref();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ─── Per-module cache interface ──────────────────────────────────────
|
|
175
|
+
class DiskCache {
|
|
176
|
+
ns;
|
|
177
|
+
ttlMs;
|
|
178
|
+
constructor(ttlMs, name) {
|
|
179
|
+
this.ttlMs = ttlMs;
|
|
180
|
+
this.ns = name;
|
|
181
|
+
}
|
|
182
|
+
getMap() {
|
|
183
|
+
loadGlobal(); // Lazy — only reads disk on first access
|
|
184
|
+
let map = _globalStore.get(this.ns);
|
|
185
|
+
if (!map) {
|
|
186
|
+
map = new Map();
|
|
187
|
+
_globalStore.set(this.ns, map);
|
|
188
|
+
}
|
|
189
|
+
return map;
|
|
190
|
+
}
|
|
191
|
+
get(key) {
|
|
192
|
+
const map = this.getMap();
|
|
193
|
+
const entry = map.get(key);
|
|
194
|
+
if (!entry)
|
|
195
|
+
return undefined;
|
|
196
|
+
if (Date.now() > entry.expires) {
|
|
197
|
+
map.delete(key);
|
|
198
|
+
_globalDirty = true;
|
|
199
|
+
scheduleGlobalWrite();
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
// Update last access for LRU
|
|
203
|
+
entry.lastAccess = Date.now();
|
|
204
|
+
return entry.data;
|
|
205
|
+
}
|
|
206
|
+
set(key, data) {
|
|
207
|
+
if (this.ttlMs <= 0)
|
|
208
|
+
return;
|
|
209
|
+
const map = this.getMap();
|
|
210
|
+
// LRU eviction if at capacity
|
|
211
|
+
if (map.size >= MAX_ENTRIES_PER_MODULE && !map.has(key)) {
|
|
212
|
+
let oldestKey;
|
|
213
|
+
let oldestAccess = Infinity;
|
|
214
|
+
for (const [k, e] of map) {
|
|
215
|
+
const access = e.lastAccess ?? e.expires - this.ttlMs;
|
|
216
|
+
if (access < oldestAccess) {
|
|
217
|
+
oldestAccess = access;
|
|
218
|
+
oldestKey = k;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (oldestKey)
|
|
222
|
+
map.delete(oldestKey);
|
|
223
|
+
}
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
map.set(key, { data, expires: now + this.ttlMs, lastAccess: now });
|
|
226
|
+
_globalDirty = true;
|
|
227
|
+
scheduleGlobalWrite();
|
|
228
|
+
}
|
|
229
|
+
clear() {
|
|
230
|
+
_globalStore.delete(this.ns);
|
|
231
|
+
_globalDirty = true;
|
|
232
|
+
scheduleGlobalWrite();
|
|
233
|
+
}
|
|
234
|
+
get size() {
|
|
235
|
+
const map = _globalStore.get(this.ns);
|
|
236
|
+
if (!map)
|
|
237
|
+
return 0;
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
let count = 0;
|
|
240
|
+
for (const entry of map.values()) {
|
|
241
|
+
if (now <= entry.expires)
|
|
242
|
+
count++;
|
|
243
|
+
}
|
|
244
|
+
return count;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ─── Fetch with timeout ──────────────────────────────────────────────
|
|
248
|
+
async function fetchTimeout(url, init, timeoutMs) {
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
251
|
+
try {
|
|
252
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ─── Retry logic ─────────────────────────────────────────────────────
|
|
259
|
+
const RETRYABLE = [429, 502, 503, 504];
|
|
260
|
+
async function fetchRetry(url, init, timeoutMs, limiter, name, maxRetries = 2) {
|
|
261
|
+
let lastErr = null;
|
|
262
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
263
|
+
await limiter.acquire();
|
|
264
|
+
try {
|
|
265
|
+
const res = await fetchTimeout(url, init, timeoutMs);
|
|
266
|
+
if (RETRYABLE.includes(res.status) && attempt < maxRetries) {
|
|
267
|
+
const retryAfter = res.headers.get("Retry-After");
|
|
268
|
+
const delay = retryAfter ? (+retryAfter || 1) * 1000 : 1000 * 2 ** attempt;
|
|
269
|
+
console.error(`${name}: HTTP ${res.status}, retry in ${delay}ms (${attempt + 1}/${maxRetries})`);
|
|
270
|
+
await new Promise(r => setTimeout(r, delay));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
return res;
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
lastErr = e instanceof Error ? e : new Error(String(e));
|
|
277
|
+
if (attempt < maxRetries) {
|
|
278
|
+
const delay = 1000 * 2 ** attempt;
|
|
279
|
+
console.error(`${name}: ${lastErr.message}, retry in ${delay}ms (${attempt + 1}/${maxRetries})`);
|
|
280
|
+
await new Promise(r => setTimeout(r, delay));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
throw lastErr ?? new Error("Request failed");
|
|
285
|
+
}
|
|
286
|
+
// ─── Client Factory ──────────────────────────────────────────────────
|
|
287
|
+
export function createClient(config) {
|
|
288
|
+
const { baseUrl, name, auth, defaultHeaders = {}, cacheTtlMs = 5 * 60 * 1000, timeoutMs = 30_000, checkError, } = config;
|
|
289
|
+
const rl = config.rateLimit ?? { perSecond: 5, burst: 10 };
|
|
290
|
+
const limiter = new TokenBucket(rl.burst, rl.perSecond);
|
|
291
|
+
const cache = new DiskCache(cacheTtlMs, name);
|
|
292
|
+
function getApiKey() {
|
|
293
|
+
return auth ? process.env[auth.envVar] : undefined;
|
|
294
|
+
}
|
|
295
|
+
function buildUrl(path, params) {
|
|
296
|
+
const parts = [];
|
|
297
|
+
// Auth via query param
|
|
298
|
+
if (auth?.type === "query") {
|
|
299
|
+
const key = getApiKey();
|
|
300
|
+
if (key)
|
|
301
|
+
parts.push(`${auth.key}=${encodeURIComponent(key)}`);
|
|
302
|
+
if (auth.extraParams) {
|
|
303
|
+
for (const [k, v] of Object.entries(auth.extraParams))
|
|
304
|
+
parts.push(`${k}=${encodeURIComponent(v)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// User params — supports string, number, and string[] (repeated keys)
|
|
308
|
+
// Keys are NOT encoded — preserves bracket syntax like page[number], facets[series][]
|
|
309
|
+
if (params) {
|
|
310
|
+
for (const [k, v] of Object.entries(params)) {
|
|
311
|
+
if (v === undefined || v === "")
|
|
312
|
+
continue;
|
|
313
|
+
if (Array.isArray(v)) {
|
|
314
|
+
for (const item of v)
|
|
315
|
+
parts.push(`${k}=${encodeURIComponent(String(item))}`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
parts.push(`${k}=${encodeURIComponent(String(v))}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
323
|
+
return parts.length ? `${baseUrl}${p}?${parts.join("&")}` : `${baseUrl}${p}`;
|
|
324
|
+
}
|
|
325
|
+
function buildHeaders(extra) {
|
|
326
|
+
const h = { ...defaultHeaders, ...extra };
|
|
327
|
+
if (auth?.type === "header") {
|
|
328
|
+
const key = getApiKey();
|
|
329
|
+
if (key)
|
|
330
|
+
h[auth.key] = (auth.prefix ?? "") + key;
|
|
331
|
+
}
|
|
332
|
+
return h;
|
|
333
|
+
}
|
|
334
|
+
async function request(url, init) {
|
|
335
|
+
// Check cache
|
|
336
|
+
const cacheKey = init?.body ? `${url}|${init.body}` : url;
|
|
337
|
+
const cached = cache.get(cacheKey);
|
|
338
|
+
if (cached !== undefined)
|
|
339
|
+
return cached;
|
|
340
|
+
const res = await fetchRetry(url, init, timeoutMs, limiter, name);
|
|
341
|
+
if (!res.ok) {
|
|
342
|
+
const body = await res.text();
|
|
343
|
+
// Friendly error for auth failures when API key is missing
|
|
344
|
+
if ((res.status === 401 || res.status === 403) && auth && !getApiKey()) {
|
|
345
|
+
throw new Error(`${name}: API key required (HTTP ${res.status}). ` +
|
|
346
|
+
`Set the ${auth.envVar} environment variable in your .env file or MCP config.`);
|
|
347
|
+
}
|
|
348
|
+
throw new Error(`${name}: HTTP ${res.status} — ${body || res.statusText}`);
|
|
349
|
+
}
|
|
350
|
+
const data = await res.json();
|
|
351
|
+
// Check for API-level errors in body
|
|
352
|
+
if (checkError) {
|
|
353
|
+
const err = checkError(data);
|
|
354
|
+
if (err)
|
|
355
|
+
throw new Error(`${name}: ${err}`);
|
|
356
|
+
}
|
|
357
|
+
cache.set(cacheKey, data);
|
|
358
|
+
return data;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
async get(path, params) {
|
|
362
|
+
const url = buildUrl(path, params);
|
|
363
|
+
const headers = buildHeaders();
|
|
364
|
+
return request(url, Object.keys(headers).length ? { headers } : undefined);
|
|
365
|
+
},
|
|
366
|
+
async post(path, body, params) {
|
|
367
|
+
const url = buildUrl(path, params);
|
|
368
|
+
const headers = buildHeaders({ "Content-Type": "application/json" });
|
|
369
|
+
// Auth via body (e.g. BLS)
|
|
370
|
+
const finalBody = { ...body };
|
|
371
|
+
if (auth?.type === "body") {
|
|
372
|
+
const key = getApiKey();
|
|
373
|
+
if (key) {
|
|
374
|
+
finalBody[auth.key] = key;
|
|
375
|
+
if (auth.extraParams)
|
|
376
|
+
Object.assign(finalBody, auth.extraParams);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return request(url, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
headers,
|
|
382
|
+
body: JSON.stringify(finalBody),
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
clearCache() { cache.clear(); },
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=client.js.map
|