gov-data-mcp 1.1.3 → 1.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # gov-data-mcp
2
2
 
3
- **120 US government open-data tools, as one MCP server.**
3
+ **122 US government open-data tools, as one MCP server.**
4
4
 
5
5
  EPA, FEMA, USGS, NOAA, FAA, USACE, FDIC, HUD, NRCS, HRSA, CMS, county assessor rolls and state licensing boards — all reachable as agent-callable tools, all reading directly from official government APIs and bulk files. No scraping, no HTML parsing, no rate-limit roulette.
6
6
 
@@ -49,13 +49,97 @@ Claude Desktop reads `claude_desktop_config.json`; Claude Code reads `.mcp.json`
49
49
  | `parcel-owner-lookup` | Assessor-roll owner of record for an address |
50
50
  | `license-verifier` | 19 professional licensing boards across 9 states + OIG exclusions |
51
51
 
52
- **Plus three tools that reach the other 108:**
52
+ **Plus three tools that reach the other 110:**
53
53
 
54
54
  - `search-gov-data-tools` — find a tool by keyword, agency or topic
55
55
  - `describe-gov-data-tool` — full input schema for any tool in the catalog
56
56
  - `run-gov-data-tool` — run any tool in the catalog
57
57
 
58
- The catalog is bundled, so discovery costs nothing. Ask your agent *"what government data tools do you have for flood risk?"* and it will search all 120.
58
+ The catalog is bundled, so discovery costs nothing. Ask your agent *"what government data tools do you have for flood risk?"* and it will search all 122.
59
+
60
+ <!-- PRESETS:START -->
61
+ ## Vertical presets
62
+
63
+ The full server is the right default for a general-purpose agent. For an agent that serves one kind of buyer, a **preset** narrows the whole surface — the named tools *and* what `search-gov-data-tools` / `describe-gov-data-tool` / `run-gov-data-tool` can reach — to one shelf, and announces itself to the client under its own server name and description. Same package, same catalog, same release; nothing to install separately. Pass `--preset <name>` or set `GOV_DATA_MCP_PRESET`. An unknown preset name refuses to start rather than guessing. `npx gov-data-mcp --list-presets` prints the current list as JSON.
64
+
65
+ | Preset | Server name | Tools | For |
66
+ |---|---|---|---|
67
+ | `phase1-esa` | `gov-data-mcp-phase1-esa` | 11 + 3 meta | Phase I ESA — Environmental Due Diligence |
68
+ | `cre-leads` | `gov-data-mcp-cre-leads` | 12 + 3 meta | CRE & Local Business Leads — Who Just Changed State |
69
+ | `compliance` | `gov-data-mcp-compliance` | 12 + 3 meta | KYB, Sanctions & Financial Compliance Screening |
70
+
71
+ ### `phase1-esa` — Phase I ESA — Environmental Due Diligence
72
+
73
+ US government environmental records for Phase I Environmental Site Assessments and property due diligence: EPA Superfund / RCRA / UST / brownfield records at ASTM E1527-21 search distances, state tank and spill registries, historic land use from FRS SIC/NAICS, USGS historical topographic map coverage, NWI wetlands, Clean Water Act §404 surface water, RCRA corrective-action cleanups, orphaned wells, FEMA hazard risk and NFIP flood-loss history, plus a twenty-layer go / caution / no-go site verdict. Every tool reads the official source directly and reports what it could not check as null, never as clear.
74
+
75
+ **11 tools:** [site-due-diligence-bundle](https://apify.com/malonestar/site-due-diligence-bundle) · [epa-contaminated-site-screener](https://apify.com/malonestar/epa-contaminated-site-screener) · [historic-land-use-sic-contaminant-screener](https://apify.com/malonestar/historic-land-use-sic-contaminant-screener) · [state-tank-spill-registry-screener](https://apify.com/malonestar/state-tank-spill-registry-screener) · [usgs-historical-topo-records-review](https://apify.com/malonestar/usgs-historical-topo-records-review) · [epa-rcra-corrective-action-cleanup-monitor](https://apify.com/malonestar/epa-rcra-corrective-action-cleanup-monitor) · [orphaned-well-proximity-screener](https://apify.com/malonestar/orphaned-well-proximity-screener) · [fws-wetlands-proximity-screener](https://apify.com/malonestar/fws-wetlands-proximity-screener) · [nhd-surface-water-404-screener](https://apify.com/malonestar/nhd-surface-water-404-screener) · [fema-nri-county-risk-profile](https://apify.com/malonestar/fema-nri-county-risk-profile) · [nfip-flood-loss-risk-screener](https://apify.com/malonestar/nfip-flood-loss-risk-screener) — plus `search-gov-data-tools`, `describe-gov-data-tool`, `run-gov-data-tool`, scoped to this list.
76
+
77
+ Claude Desktop (`claude_desktop_config.json`), Claude Code (`.mcp.json`) and Cursor (`.cursor/mcp.json`) all take the same block:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "gov-data-phase1-esa": {
83
+ "command": "npx",
84
+ "args": ["-y", "gov-data-mcp", "--preset", "phase1-esa"],
85
+ "env": { "APIFY_TOKEN": "apify_api_..." }
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ Or, with the environment variable instead of the flag: `"args": ["-y", "gov-data-mcp"]` and `"env": { "APIFY_TOKEN": "apify_api_...", "GOV_DATA_MCP_PRESET": "phase1-esa" }`.
92
+
93
+ Command line: `npx gov-data-mcp --preset phase1-esa`
94
+
95
+ ### `cre-leads` — CRE & Local Business Leads — Who Just Changed State
96
+
97
+ Lead-generation feeds built from official state and county registers: new and pending liquor licenses, verified new business openings from Secretary of State and tax rosters, WARN layoff notices, newly licensed real estate agents, licensed childcare providers with contacts, parcel owner of record, absentee-owner lists, distressed-property signal stacks, NYC deed transfers and landlord registries, city business licenses and professional license verification. Delta modes return only what changed since the last run, so a scheduled agent pays for new leads, not the same roster twice.
98
+
99
+ **12 tools:** [liquor-license-new-openings-tracker](https://apify.com/malonestar/liquor-license-new-openings-tracker) · [sos-registry-monitor](https://apify.com/malonestar/sos-registry-monitor) · [warn-layoff-aggregator](https://apify.com/malonestar/warn-layoff-aggregator) · [realtor-license-roster-delta](https://apify.com/malonestar/realtor-license-roster-delta) · [childcare-provider-leads](https://apify.com/malonestar/childcare-provider-leads) · [parcel-owner-lookup](https://apify.com/malonestar/parcel-owner-lookup) · [absentee-owner-lead-list-builder](https://apify.com/malonestar/absentee-owner-lead-list-builder) · [distressed-property-signal-stacker](https://apify.com/malonestar/distressed-property-signal-stacker) · [acris-deed-transfer-intel](https://apify.com/malonestar/acris-deed-transfer-intel) · [nyc-landlord-registry-lead-list](https://apify.com/malonestar/nyc-landlord-registry-lead-list) · [city-business-license-leads](https://apify.com/malonestar/city-business-license-leads) · [license-verifier](https://apify.com/malonestar/license-verifier) — plus `search-gov-data-tools`, `describe-gov-data-tool`, `run-gov-data-tool`, scoped to this list.
100
+
101
+ Claude Desktop (`claude_desktop_config.json`), Claude Code (`.mcp.json`) and Cursor (`.cursor/mcp.json`) all take the same block:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "gov-data-cre-leads": {
107
+ "command": "npx",
108
+ "args": ["-y", "gov-data-mcp", "--preset", "cre-leads"],
109
+ "env": { "APIFY_TOKEN": "apify_api_..." }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ Or, with the environment variable instead of the flag: `"args": ["-y", "gov-data-mcp"]` and `"env": { "APIFY_TOKEN": "apify_api_...", "GOV_DATA_MCP_PRESET": "cre-leads" }`.
116
+
117
+ Command line: `npx gov-data-mcp --preset cre-leads`
118
+
119
+ ### `compliance` — KYB, Sanctions & Financial Compliance Screening
120
+
121
+ Know-your-business and compliance screening from primary US government registers: company existence across Secretary of State registries, the Trade.gov Consolidated Screening List (OFAC SDN, BIS, State) as a delta, the DHS UFLPA Entity List, state Medicaid and HHS-OIG exclusion lists, professional license verification, GLEIF LEI ownership graphs, FDIC / NCUA institution health, FDIC structure changes, SEC investment-adviser registrations, PCAOB auditor engagements and SEC Regulation CF / A+ offering lifecycles. A screen that could not reach its source fails loudly rather than returning a clean sheet.
122
+
123
+ **12 tools:** [kyb-company-verifier](https://apify.com/malonestar/kyb-company-verifier) · [consolidated-screening-list-delta](https://apify.com/malonestar/consolidated-screening-list-delta) · [uflpa-entity-list-monitor](https://apify.com/malonestar/uflpa-entity-list-monitor) · [medicaid-exclusion-screener](https://apify.com/malonestar/medicaid-exclusion-screener) · [license-verifier](https://apify.com/malonestar/license-verifier) · [gleif-ownership-graph](https://apify.com/malonestar/gleif-ownership-graph) · [fdic-ncua-health-rollup](https://apify.com/malonestar/fdic-ncua-health-rollup) · [fdic-structure-change-delta-monitor](https://apify.com/malonestar/fdic-structure-change-delta-monitor) · [ria-registration-delta-monitor](https://apify.com/malonestar/ria-registration-delta-monitor) · [pcaob-auditor-engagement-monitor](https://apify.com/malonestar/pcaob-auditor-engagement-monitor) · [reg-cf-lifecycle-monitor](https://apify.com/malonestar/reg-cf-lifecycle-monitor) · [reg-a-plus-lifecycle-monitor](https://apify.com/malonestar/reg-a-plus-lifecycle-monitor) — plus `search-gov-data-tools`, `describe-gov-data-tool`, `run-gov-data-tool`, scoped to this list.
124
+
125
+ Claude Desktop (`claude_desktop_config.json`), Claude Code (`.mcp.json`) and Cursor (`.cursor/mcp.json`) all take the same block:
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "gov-data-compliance": {
131
+ "command": "npx",
132
+ "args": ["-y", "gov-data-mcp", "--preset", "compliance"],
133
+ "env": { "APIFY_TOKEN": "apify_api_..." }
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ Or, with the environment variable instead of the flag: `"args": ["-y", "gov-data-mcp"]` and `"env": { "APIFY_TOKEN": "apify_api_...", "GOV_DATA_MCP_PRESET": "compliance" }`.
140
+
141
+ Command line: `npx gov-data-mcp --preset compliance`
142
+ <!-- PRESETS:END -->
59
143
 
60
144
  ## Example prompts
61
145
 
@@ -103,7 +187,7 @@ The underlying actors carry the same discipline: per-source status on every row,
103
187
  <!-- COVERAGE:START -->
104
188
  ## Coverage
105
189
 
106
- **120 tools**, every one reading an official US government API or bulk file. The MCP server exposes 15 of them directly — twelve named tools plus `search-gov-data-tools`, `describe-gov-data-tool` and `run-gov-data-tool`, which reach the rest — because agents choose badly when handed more than about twenty tools.
190
+ **122 tools**, every one reading an official US government API or bulk file. The MCP server exposes 15 of them directly — twelve named tools plus `search-gov-data-tools`, `describe-gov-data-tool` and `run-gov-data-tool`, which reach the rest — because agents choose badly when handed more than about twenty tools.
107
191
 
108
192
  Each entry links to its full input/output schema, pricing and worked examples.
109
193
 
@@ -127,9 +211,9 @@ Each entry links to its full input/output schema, pricing and worked examples.
127
211
 
128
212
  [ssurgo-soil-suitability-screener](https://apify.com/malonestar/ssurgo-soil-suitability-screener) · [usda-cdl-farmland-siting-screener](https://apify.com/malonestar/usda-cdl-farmland-siting-screener) · [usgs-groundwater-depth-screener](https://apify.com/malonestar/usgs-groundwater-depth-screener) · [usgs-nwis-streamflow-monitor](https://apify.com/malonestar/usgs-nwis-streamflow-monitor) · [water-rights-availability-screener](https://apify.com/malonestar/water-rights-availability-screener)
129
213
 
130
- **Banking, lending & credit** (8)
214
+ **Banking, lending & credit** (9)
131
215
 
132
- [bank-enforcement-tracker](https://apify.com/malonestar/bank-enforcement-tracker) · [fdic-branch-network-churn-rollup](https://apify.com/malonestar/fdic-branch-network-churn-rollup) · [fdic-ncua-health-rollup](https://apify.com/malonestar/fdic-ncua-health-rollup) · [fdic-sod-deposit-market-share-rollup](https://apify.com/malonestar/fdic-sod-deposit-market-share-rollup) · [fdic-structure-change-delta-monitor](https://apify.com/malonestar/fdic-structure-change-delta-monitor) · [fhlbank-membership-delta-monitor](https://apify.com/malonestar/fhlbank-membership-delta-monitor) · [hmda-fair-lending-disparity-rollup](https://apify.com/malonestar/hmda-fair-lending-disparity-rollup) · [sba-loan-portfolio-explorer](https://apify.com/malonestar/sba-loan-portfolio-explorer)
216
+ [bank-enforcement-tracker](https://apify.com/malonestar/bank-enforcement-tracker) · [fdic-branch-network-churn-rollup](https://apify.com/malonestar/fdic-branch-network-churn-rollup) · [fdic-ncua-health-rollup](https://apify.com/malonestar/fdic-ncua-health-rollup) · [fdic-sod-deposit-market-share-rollup](https://apify.com/malonestar/fdic-sod-deposit-market-share-rollup) · [fdic-structure-change-delta-monitor](https://apify.com/malonestar/fdic-structure-change-delta-monitor) · [fhlbank-membership-delta-monitor](https://apify.com/malonestar/fhlbank-membership-delta-monitor) · [hmda-fair-lending-disparity-rollup](https://apify.com/malonestar/hmda-fair-lending-disparity-rollup) · [sba-loan-portfolio-explorer](https://apify.com/malonestar/sba-loan-portfolio-explorer) · [ucc-lapse-refinance-window](https://apify.com/malonestar/ucc-lapse-refinance-window)
133
217
 
134
218
  **Securities, audit, pensions & sanctions** (10)
135
219
 
@@ -151,9 +235,9 @@ Each entry links to its full input/output schema, pricing and worked examples.
151
235
 
152
236
  [cdc-nndss-outbreak-monitor](https://apify.com/malonestar/cdc-nndss-outbreak-monitor) · [clinical-trials-meta-search](https://apify.com/malonestar/clinical-trials-meta-search) · [cms-open-payments](https://apify.com/malonestar/cms-open-payments) · [cms-part-d-prescriber-delta](https://apify.com/malonestar/cms-part-d-prescriber-delta) · [drug-shortage-delta-monitor](https://apify.com/malonestar/drug-shortage-delta-monitor) · [nadac-price-movers](https://apify.com/malonestar/nadac-price-movers) · [usmin-mine-feature-proximity-screener](https://apify.com/malonestar/usmin-mine-feature-proximity-screener)
153
237
 
154
- **Patents, IP & company data** (4)
238
+ **Patents, IP & company data** (5)
155
239
 
156
- [npm-package-health-scorer](https://apify.com/malonestar/npm-package-health-scorer) · [ptab-trial-tracker](https://apify.com/malonestar/ptab-trial-tracker) · [sbom-vulnerability-rollup](https://apify.com/malonestar/sbom-vulnerability-rollup) · [uspto-patent-lapse-fto-monitor](https://apify.com/malonestar/uspto-patent-lapse-fto-monitor)
240
+ [ca-data-broker-registry-delta](https://apify.com/malonestar/ca-data-broker-registry-delta) · [npm-package-health-scorer](https://apify.com/malonestar/npm-package-health-scorer) · [ptab-trial-tracker](https://apify.com/malonestar/ptab-trial-tracker) · [sbom-vulnerability-rollup](https://apify.com/malonestar/sbom-vulnerability-rollup) · [uspto-patent-lapse-fto-monitor](https://apify.com/malonestar/uspto-patent-lapse-fto-monitor)
157
241
 
158
242
  **Labor & enforcement** (3)
159
243
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gov-data-mcp",
3
- "version": "1.1.3",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.malonestar/gov-data-mcp",
5
5
  "description": "MCP server exposing published US government open-data tools (EPA, FEMA, USGS, NOAA, FAA, USACE, FDIC, HUD, state licensing boards) as agent-callable tools, backed by Apify Actors.",
6
6
  "type": "module",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "scripts": {
20
20
  "start": "node src/index.js",
21
- "test": "node --test test/tools.test.js test/apify.test.js test/catalog.test.js test/agent-surface.test.js",
21
+ "test": "node --test test/tools.test.js test/apify.test.js test/catalog.test.js test/agent-surface.test.js test/presets.test.js",
22
22
  "catalog": "node tools/gen-catalog.cjs",
23
23
  "check:catalog": "node tools/check-catalog-live.cjs",
24
24
  "readme": "node tools/gen-readme-coverage.cjs"
package/src/catalog.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "generatedAt": "2026-09-24T04:13:45.505Z",
2
+ "generatedAt": "2026-10-06T01:02:08.793Z",
3
3
  "owner": "malonestar",
4
- "count": 120,
4
+ "count": 122,
5
5
  "actors": [
6
6
  {
7
7
  "slug": "absentee-owner-lead-list-builder",
@@ -84,7 +84,7 @@
84
84
  "categories": [
85
85
  "REAL_ESTATE",
86
86
  "LEAD_GENERATION",
87
- "DEVELOPER_TOOLS"
87
+ "BUSINESS"
88
88
  ],
89
89
  "storeUrl": "https://apify.com/malonestar/acris-deed-transfer-intel",
90
90
  "pricing": {
@@ -770,6 +770,99 @@
770
770
  }
771
771
  }
772
772
  },
773
+ {
774
+ "slug": "ca-data-broker-registry-delta",
775
+ "actorId": "malonestar/ca-data-broker-registry-delta",
776
+ "title": "California Data Broker Registry Monitor — CPPA Delete Act Delta",
777
+ "description": "California data broker registry API (CPPA / Delete Act): every registered data broker with 2026 disclosure flags (GenAI, foreign actor, minors, geolocation, biometric), DSAR metrics, first-registered year, new/lapsed/renamed/changed delta and is-this-company-registered verification.",
778
+ "categories": [
779
+ "BUSINESS",
780
+ "DEVELOPER_TOOLS",
781
+ "AGENTS"
782
+ ],
783
+ "storeUrl": "https://apify.com/malonestar/ca-data-broker-registry-delta",
784
+ "pricing": {
785
+ "model": "PAY_PER_EVENT",
786
+ "event": "apify-default-dataset-item",
787
+ "unit": "Data broker registry row",
788
+ "usdPerUnit": 0.01,
789
+ "usdPer1000": 10,
790
+ "tierDiscountsUsdPerUnit": {
791
+ "FREE": 0.01,
792
+ "BRONZE": 0.008,
793
+ "SILVER": 0.007,
794
+ "GOLD": 0.0055,
795
+ "PLATINUM": 0.0055,
796
+ "DIAMOND": 0.0055
797
+ },
798
+ "actorStartUsd": null,
799
+ "since": "2026-09-26T22:00:01.299Z"
800
+ },
801
+ "inputSchema": {
802
+ "type": "object",
803
+ "properties": {
804
+ "mode": {
805
+ "type": "string",
806
+ "description": "registry = the full CPPA registrant list (filterable) with 2026 disclosure flags, DSAR metrics and registration history. delta = only what changed since your last run: new, lapsed, renamed and changed registrants (the first run compares against CPPA's own 2025 export, so it is a real delta). verify = check names[] against the registry and get a three-valued is_registered per name. Example: \"registry\".",
807
+ "enum": [
808
+ "registry",
809
+ "delta",
810
+ "verify"
811
+ ]
812
+ },
813
+ "flags": {
814
+ "type": "array",
815
+ "description": "Optional. Keep only registrants that filed Yes on EVERY listed flag. Keys: collects_minors, collects_account_logins, collects_government_ids, collects_citizenship_data, collects_union_membership, collects_sexual_orientation, collects_gender_identity, collects_biometric, collects_precise_geolocation, collects_reproductive_health, sold_to_foreign_actor, sold_to_federal_government, sold_to_state_governments, sold_to_law_enforcement, sold_to_genai_developer, regulated_fcra, regulated_glba, regulated_iippa, regulated_cmia, regulated_hipaa. An unknown key fails the run before any download.",
816
+ "items": {
817
+ "type": "string"
818
+ }
819
+ },
820
+ "nameQuery": {
821
+ "type": "string",
822
+ "description": "Optional substring match on the registered name or DBA (case, punctuation and Inc/LLC suffixes ignored). Example: \"transunion\"."
823
+ },
824
+ "websiteQuery": {
825
+ "type": "string",
826
+ "description": "Optional substring match on the filed website, e.g. \"acxiom.com\"."
827
+ },
828
+ "state": {
829
+ "type": "string",
830
+ "description": "Optional. 2-letter code or full name (\"CA\" and \"California\" both match the same registrants — CPPA stores both spellings)."
831
+ },
832
+ "country": {
833
+ "type": "string",
834
+ "description": "Optional exact match on the filed country, as CPPA publishes it in upper case (UNITED STATES, UNITED KINGDOM, CANADA...)."
835
+ },
836
+ "eventTypes": {
837
+ "type": "array",
838
+ "description": "delta mode only. Optional subset of new, lapsed, renamed, changed. Empty = all four.",
839
+ "items": {
840
+ "type": "string"
841
+ }
842
+ },
843
+ "names": {
844
+ "type": "array",
845
+ "description": "verify mode. One entry per company; you get one row per entry with is_registered true / false / null (null = fuzzy candidates exist, see evidence — review, do not treat as negative). Max 500. Example: [\"Acxiom\",\"LexisNexis\",\"Oracle America\"].",
846
+ "items": {
847
+ "type": "string"
848
+ }
849
+ },
850
+ "includeHistory": {
851
+ "type": "boolean",
852
+ "description": "Also read CPPA's 2024 and 2025 exports and the legacy OAG 2020-2023 registry to fill first_registered_year / date, registration_years_seen and in_registry_* on every row. Three extra small downloads. Default on. Applied by default if omitted: true."
853
+ },
854
+ "maxResults": {
855
+ "type": "integer",
856
+ "description": "Hard cap on rows emitted and billed (1-5000). The full registry is ~600 rows; every row carries matched_rows_total and results_truncated so a capped answer is never mistaken for the whole list. Example: 100.",
857
+ "minimum": 1,
858
+ "maximum": 5000
859
+ }
860
+ },
861
+ "required": [
862
+ "mode"
863
+ ]
864
+ }
865
+ },
773
866
  {
774
867
  "slug": "calfire-fhsz-screener",
775
868
  "actorId": "malonestar/calfire-fhsz-screener",
@@ -1055,11 +1148,11 @@
1055
1148
  "slug": "childcare-provider-leads",
1056
1149
  "actorId": "malonestar/childcare-provider-leads",
1057
1150
  "title": "Childcare Provider Leads — Daycare Contact Lists + Compliance",
1058
- "description": "Licensed childcare provider lead lists from official state licensing data with direct phone AND email contacts (Texas HHSC). Filter by county, type, capacity, ages served; each lead scored with 2-year deficiency counts + high-risk flag. Delta mode alerts on newly licensed operations.",
1151
+ "description": "Licensed childcare provider lead lists from official state licensing data in 9 states: TX, NY, CA, PA, MA, CT, WA, DE, VT. Phone, email and named contacts where published; filter by county, type, capacity, ages. Violation history where the state publishes it. Delta mode flags new programs.",
1059
1152
  "categories": [
1060
1153
  "LEAD_GENERATION",
1061
1154
  "AGENTS",
1062
- "DEVELOPER_TOOLS"
1155
+ "BUSINESS"
1063
1156
  ],
1064
1157
  "storeUrl": "https://apify.com/malonestar/childcare-provider-leads",
1065
1158
  "pricing": {
@@ -1084,26 +1177,63 @@
1084
1177
  "properties": {
1085
1178
  "state": {
1086
1179
  "type": "string",
1087
- "description": "REQUIRED. US state to pull licensed childcare providers from. v1 supports TX (Texas HHSC Child Care Licensing); FL and CA are scaffolded but not enabled. Name the state explicitly rather than relying on a default. Example: \"TX\".",
1180
+ "description": "REQUIRED. US state to pull licensed childcare providers from: TX, NY, CA, PA, MA, CT, WA, DE or VT. Name the state explicitly rather than relying on a default. To pull several states in one run, also fill \"Additional states\". Example: \"TX\".",
1088
1181
  "enum": [
1089
- "TX"
1182
+ "TX",
1183
+ "NY",
1184
+ "CA",
1185
+ "PA",
1186
+ "MA",
1187
+ "CT",
1188
+ "WA",
1189
+ "DE",
1190
+ "VT"
1090
1191
  ]
1091
1192
  },
1193
+ "states": {
1194
+ "type": "array",
1195
+ "description": "Optional extra states to include in the same run, in addition to \"State\". maxResults is shared fairly across all requested states (a state that runs out of matches hands its unused share to the others). A state that cannot answer - source down, failed live check, or a filter it cannot apply - is skipped and disclosed on every row (run_states_failed / run_states_failed_reason); the run fails only if no requested state can answer.",
1196
+ "items": {
1197
+ "type": "string",
1198
+ "enum": [
1199
+ "TX",
1200
+ "NY",
1201
+ "CA",
1202
+ "PA",
1203
+ "MA",
1204
+ "CT",
1205
+ "WA",
1206
+ "DE",
1207
+ "VT"
1208
+ ],
1209
+ "enumTitles": [
1210
+ "Texas (HHSC CCL — phone + email + violations)",
1211
+ "New York (OCFS + NYC DOHMH — phone)",
1212
+ "California (CDSS CCLD centers — phone + citations)",
1213
+ "Pennsylvania (OCDEL — phone + email + STARS)",
1214
+ "Massachusetts (EEC — phone)",
1215
+ "Connecticut (OEC — phone + inspections + violations)",
1216
+ "Washington (DCYF centers — email + contact)",
1217
+ "Delaware (OCCL — phone + citations)",
1218
+ "Vermont (CDD — phone + email + STARS + vacancies)"
1219
+ ]
1220
+ }
1221
+ },
1092
1222
  "county": {
1093
1223
  "type": "string",
1094
- "description": "Filter to one county (case-insensitive, e.g. \"Harris\", \"Dallas\", \"Travis\"). Leave empty for statewide. A county that does not exist returns 0 rows, which is a valid answer. Example: \"Harris\"."
1224
+ "description": "Filter to one county (case-insensitive, e.g. \"Harris\", \"Kings\" or \"Brooklyn\", \"Los Angeles\", \"Chester\"). Leave empty for statewide. Connecticut and Massachusetts publish NO county field, so a county filter makes those states fail loudly rather than return an unfiltered statewide list. A county that does not exist returns 0 rows, which is a valid answer. Example: \"Harris\"."
1095
1225
  },
1096
1226
  "operationType": {
1097
1227
  "type": "string",
1098
- "description": "Exact operation type (case-insensitive). Common TX values: \"Licensed Center\", \"Licensed Child-Care Home\", \"Registered Child-Care Home\", \"Listed Family Home\". Leave empty for all types. Example: \"Licensed Center\"."
1228
+ "description": "Exact operation type as the state names it (case-insensitive). Examples - TX: \"Licensed Center\", \"Licensed Child-Care Home\"; NY: \"Day Care Center\", \"Group Family Day Care\", \"Family Day Care\", \"School Age Child Care\" (or DCC/GFDC/FDC/SACC); PA: \"Child Care Center\", \"Family Child Care Home\", \"Group Child Care Home\"; CT: \"Child Care Center\", \"Family Child Care Home\"; DE: \"Licensed Child Care Center\"; VT: \"CBCCPP\", \"Registered FCCH\", \"Licensed Provider\"; WA: \"CHILD CARE CENTER\", \"SCHOOL-AGE PROGRAM\"; MA: \"Center-based Care\", \"Family Child Care\"; CA: \"DAY CARE CENTER\", \"INFANT CENTER\", \"SCHOOL-AGE DC CENTER\". Leave empty for all types. Example: \"Licensed Center\"."
1099
1229
  },
1100
1230
  "agesServed": {
1101
1231
  "type": "string",
1102
- "description": "Substring match on the licensed-to-serve ages field, e.g. \"Infant\", \"Toddler\", \"Pre-Kindergarten\", \"School\". Leave empty for any."
1232
+ "description": "Ages-served filter, e.g. \"Infant\", \"Toddler\", \"Pre-Kindergarten\", \"School\". TX and DE match the published ages text; NY and VT match programs with licensed capacity in that age group (Infant, Toddler, Preschool, School). PA, CT, WA, MA and CA publish no usable ages field, so this filter makes those states fail loudly rather than return unfiltered rows. Leave empty for any."
1103
1233
  },
1104
1234
  "minCapacity": {
1105
1235
  "type": "integer",
1106
- "description": "Only include operations whose published total licensed capacity is at or above this number (e.g. 50 targets larger centers). 0 = no minimum. Operations for which TX publishes NO capacity are KEPT and marked capacity_filter_applied=\"kept_unknown_capacity\" rather than silently deleted - 141 of the 236 Child Placing Agencies statewide have no published capacity. Applied by default if omitted: 0.",
1236
+ "description": "Only include operations whose published total licensed capacity is at or above this number (e.g. 50 targets larger centers). 0 = no minimum. Operations for which the state publishes NO capacity are KEPT and marked capacity_filter_applied=\"kept_unknown_capacity\" rather than silently deleted - e.g. 141 of the 236 Texas Child Placing Agencies have no published capacity. Applied by default if omitted: 0.",
1107
1237
  "minimum": 0,
1108
1238
  "maximum": 10000
1109
1239
  },
@@ -1115,7 +1245,7 @@
1115
1245
  },
1116
1246
  "includeCompliance": {
1117
1247
  "type": "boolean",
1118
- "description": "Also join each provider to the TX non-compliance table for a 2-year window and add deficiency_count_2y, deficiency_weighted_score_2y, recent_deficiency_dates and high_risk_flag. When this is off, those four fields are NULL (not checked) - but deficiency_count_total, deficiency_weighted_score_total and high_risk_flag_lifetime, which come from HHSC counts published on the roster row itself, are emitted either way. Example: true."
1248
+ "description": "Also attach each state's published compliance record. TX: 2-year non-compliance join (deficiency_count_2y, high_risk_flag ...) - the lifetime counts HHSC publishes on the roster row are emitted either way. CT: inspection + violation history. DE: non-compliance findings (last ~5 years). CA: CCLD facility page (status, contact, licensee, inspections, Type A/B citations, complaints). NY, PA, VT, WA and MA publish no compliance dataset: those rows read violations_status=\"not_published\" with NULL counts, never 0. Example: true."
1119
1249
  },
1120
1250
  "highRiskThreshold": {
1121
1251
  "type": "integer",
@@ -1125,7 +1255,7 @@
1125
1255
  },
1126
1256
  "mode": {
1127
1257
  "type": "string",
1128
- "description": "\"roster\" (default) returns the current filtered provider list. \"new_operations\" keeps a baseline of seen operation IDs in the NAMED key-value store \"childcare-provider-leads-state\", scoped to your filter combination, and after the first seeding run emits only newly licensed operations. Every row carries delta_baseline_status so a seeding run is distinguishable from an incremental one. Applied by default if omitted: \"roster\".",
1258
+ "description": "\"roster\" (default) returns the current filtered provider list. \"new_operations\" keeps a baseline of seen operation IDs in the NAMED key-value store \"childcare-provider-leads-state\", scoped to your state and filter combination (one baseline per state), and after the first seeding run emits only newly licensed operations. Every row carries delta_baseline_status so a seeding run is distinguishable from an incremental one. Applied by default if omitted: \"roster\".",
1129
1259
  "enum": [
1130
1260
  "roster",
1131
1261
  "new_operations"
@@ -1133,7 +1263,7 @@
1133
1263
  },
1134
1264
  "activeOnly": {
1135
1265
  "type": "boolean",
1136
- "description": "Only include currently active operations (operation_status = Y). Disable to include all statuses. Applied by default if omitted: true."
1266
+ "description": "Only include currently operating programs (TX operation_status = Y; NY not Suspended; CT ACTIVE; WA Active; MA not Expired). PA, DE, VT and CA publish current rosters only. Disable to include all statuses where the state publishes them. Applied by default if omitted: true."
1137
1267
  }
1138
1268
  },
1139
1269
  "required": [
@@ -1148,7 +1278,7 @@
1148
1278
  "description": "New business-license lead feed across Chicago, LA, SF, NYC and Seattle. Normalizes municipal open data into one schema with new-vs-renewal and active-vs-dead licence flags, owner mailing address, NAICS, phone where published, plus per-city coverage and freshness on every row.",
1149
1279
  "categories": [
1150
1280
  "LEAD_GENERATION",
1151
- "DEVELOPER_TOOLS",
1281
+ "BUSINESS",
1152
1282
  "AGENTS"
1153
1283
  ],
1154
1284
  "storeUrl": "https://apify.com/malonestar/city-business-license-leads",
@@ -5445,7 +5575,7 @@
5445
5575
  "description": "KYB/AML company verification API. Given a company name, fans out to France (INSEE/RNE), GLEIF LEI, and US state registries and returns, per registry: exists, normalized status, jurisdiction, identifiers (SIREN/LEI), officers, address, and a match-confidence score. Keyless.",
5446
5576
  "categories": [
5447
5577
  "LEAD_GENERATION",
5448
- "DEVELOPER_TOOLS",
5578
+ "BUSINESS",
5449
5579
  "AGENTS"
5450
5580
  ],
5451
5581
  "storeUrl": "https://apify.com/malonestar/kyb-company-verifier",
@@ -5500,7 +5630,7 @@
5500
5630
  "slug": "license-verifier",
5501
5631
  "actorId": "malonestar/license-verifier",
5502
5632
  "title": "License Verification API — Nurses, MDs & OIG Exclusions",
5503
- "description": "Primary source verification for US professional licenses. Search 19 state boards by name or license number: status, expiration, disciplinary actions. Cross-checks the NPPES NPI registry and screens the HHS-OIG exclusion list. Bulk roster screening.",
5633
+ "description": "Primary source verification for US professional licenses. Search 19 state boards by name or license number: status, expiration, disciplinary actions. Cross-checks the NPPES NPI registry and screens the HHS-OIG exclusion list. Bulk roster screening. Newly licensed clinicians feed (roster-delta).",
5504
5634
  "categories": [
5505
5635
  "BUSINESS",
5506
5636
  "AUTOMATION",
@@ -5604,6 +5734,26 @@
5604
5734
  "items": {
5605
5735
  "type": "string"
5606
5736
  }
5737
+ },
5738
+ "mode": {
5739
+ "type": "string",
5740
+ "description": "Leave empty for the classic lookup/roster behaviour. Set \"roster-delta\" for the newly-credentialed feed: every credential ORIGINALLY ISSUED in the last sinceDays on the selected boards, one row each, with NPI cross-walk and OIG screen. The first run seeds a named baseline and emits the window as event_type \"inventory\"; later runs emit only event_type \"newly_licensed\" (a credential absent from the baseline AND issued on/after the previous run minus 7 days). A run against unchanged data emits 0 rows and bills nothing. Boards with an original-issue date: WA (DOH), TX-BON, TX-LVN, TX-APRN, IL, CO, CT, DE, OR (CCB), WA-CPA, WA-CONTRACTOR, NY-NOTARY, NY-COS, NY-APPRAISER. In this mode maxResults is the TOTAL row cap for the run (newest credentials first).",
5741
+ "enum": [
5742
+ "roster-delta"
5743
+ ]
5744
+ },
5745
+ "professions": {
5746
+ "type": "array",
5747
+ "description": "roster-delta only. Restrict to these professions, matched case-insensitively on the normalised profession (e.g. \"Registered Nurse\") or as a prefix of the board's raw credential type (\"Registered Nurse\" reaches \"Registered Nurse License\" and \"Registered Nurse Temporary Practice Permit\" but NOT \"Advanced Registered Nurse Practitioner\"). Examples: \"Registered Nurse\", \"Licensed Practical Nurse\", \"Physician And Surgeon\", \"Dentist\", \"Pharmacist\", \"Physical Therapist\". Omit for every profession the board publishes. Changing this filter starts a NEW delta baseline (the baseline is scoped by boards + professions). Example: [\"Registered Nurse\"].",
5748
+ "items": {
5749
+ "type": "string"
5750
+ }
5751
+ },
5752
+ "sinceDays": {
5753
+ "type": "integer",
5754
+ "description": "roster-delta only. How many days back the original-issue-date window reaches (1-400). The seeding run emits this whole window as inventory; later runs emit only credentials issued since the previous run (minus a 7-day publication slack). A non-integer or out-of-range value fails the run before any request is made. Example: 30.",
5755
+ "minimum": 1,
5756
+ "maximum": 400
5607
5757
  }
5608
5758
  }
5609
5759
  }
@@ -5612,11 +5762,11 @@
5612
5762
  "slug": "liquor-license-new-openings-tracker",
5613
5763
  "actorId": "malonestar/liquor-license-new-openings-tracker",
5614
5764
  "title": "Liquor License New Openings Tracker — Bar & Restaurant Leads",
5615
- "description": "Pre-opening bar & restaurant leads from state liquor registries. NEW licenses + PENDING applications from TX TABC, NY SLA, FL DBPR and CA ABC, with California new-premises vs transfer flags. Delta mode emits only new applications, newly issued licenses and status changes.",
5765
+ "description": "Bar & restaurant lead events from state liquor registries (TX, NY, FL, CA, WA, OR, CT, CO, MO, Chicago): new_application, newly_issued and closing_detected (expired, surrendered, revoked). Texas openings verified against Comptroller tax receipts (opened_verified). Delta mode emits only changes.",
5616
5766
  "categories": [
5617
5767
  "LEAD_GENERATION",
5618
5768
  "AGENTS",
5619
- "DEVELOPER_TOOLS"
5769
+ "BUSINESS"
5620
5770
  ],
5621
5771
  "storeUrl": "https://apify.com/malonestar/liquor-license-new-openings-tracker",
5622
5772
  "pricing": {
@@ -5641,30 +5791,43 @@
5641
5791
  "properties": {
5642
5792
  "states": {
5643
5793
  "type": "array",
5644
- "description": "Which state liquor-license registries to query. Available: TX (TABC active + pending), NY (SLA active + pending), FL (DBPR licensed extract, active only), CA (ABC daily export, active + pending; CA applications carry no date, so a sinceDate cannot be applied to CA pending). Example: [\"TX\",\"NY\"].",
5794
+ "description": "Which liquor-license registries to query. TX (TABC active + pending), NY (SLA active + pending), FL (DBPR, active only), CA (ABC daily export, active + pending; CA applications carry no date), CO (LED recently approved licenses, active only), MO (ATC new licenses, active only), IL-CHICAGO (City of Chicago newly issued liquor licenses, active only), WA (LCB new-application notices, last 30 days, pending only), OR (OLCC applications in progress, pending only), CT (DCP pending permits, pending only; no date published). A state that lacks the requested feed is reported on every row (run_legs_failed) rather than silently skipped; the run fails only if NO requested state can answer. Example: [\"TX\",\"NY\"].",
5645
5795
  "items": {
5646
5796
  "type": "string",
5647
5797
  "enum": [
5648
5798
  "TX",
5649
5799
  "NY",
5650
5800
  "FL",
5651
- "CA"
5801
+ "CA",
5802
+ "CO",
5803
+ "OR",
5804
+ "CT",
5805
+ "MO",
5806
+ "WA",
5807
+ "IL-CHICAGO"
5652
5808
  ],
5653
5809
  "enumTitles": [
5654
5810
  "Texas (TABC)",
5655
5811
  "New York (SLA)",
5656
5812
  "Florida (DBPR ABT)",
5657
- "California (ABC)"
5813
+ "California (ABC)",
5814
+ "Colorado (LED)",
5815
+ "Oregon (OLCC)",
5816
+ "Connecticut (DCP)",
5817
+ "Missouri (ATC)",
5818
+ "Washington (LCB)",
5819
+ "Chicago, IL (city liquor licenses)"
5658
5820
  ]
5659
5821
  }
5660
5822
  },
5661
5823
  "feed": {
5662
5824
  "type": "string",
5663
- "description": "Which feed to pull. \"pending\" = new license APPLICATIONS (the earliest opening signal — a bar/restaurant typically opens 60-90 days later). \"active\" = recently ISSUED licenses (sorted newest first). \"all\" = both. Note: FL only has an active feed. Example: \"pending\".",
5825
+ "description": "Which feed to pull. \"pending\" = new license APPLICATIONS (the earliest opening signal -- a bar/restaurant typically opens 60-90 days later). \"active\" = recently ISSUED licenses (sorted newest first). \"all\" = pending + active (unchanged: it does NOT include closing). \"closing\" = licenses that EXPIRED, were SURRENDERED, CANCELLED or REVOKED, or are lapsing without a renewal -- a bar, restaurant or store going dark (newest first; each closing row also bills one closing-detected event). Pending is published by TX, NY, CA, WA, OR, CT; active by TX, NY, FL, CA, CO, MO and Chicago; closing by TX, OR and Chicago only -- any other requested state is reported on every row as unable to answer the closing feed, and the run fails if none can. Example: \"pending\".",
5664
5826
  "enum": [
5665
5827
  "pending",
5666
5828
  "active",
5667
- "all"
5829
+ "all",
5830
+ "closing"
5668
5831
  ]
5669
5832
  },
5670
5833
  "mode": {
@@ -5677,21 +5840,25 @@
5677
5840
  },
5678
5841
  "city": {
5679
5842
  "type": "string",
5680
- "description": "Optional case-insensitive city substring filter (e.g. \"Austin\", \"Brooklyn\", \"Miami\"). Applied server-side on Socrata sources and client-side on the FL CSV."
5843
+ "description": "Optional case-insensitive city substring filter (e.g. \"Austin\", \"Brooklyn\", \"Seattle\"). Oregon applications publish no separate city, so for OR the match is against the one-line address. A source that publishes no city refuses the filter for its leg (disclosed on every row) instead of ignoring it."
5681
5844
  },
5682
5845
  "county": {
5683
5846
  "type": "string",
5684
- "description": "Optional case-insensitive county substring filter (e.g. \"Bexar\", \"Bronx\")."
5847
+ "description": "Optional case-insensitive county substring filter (e.g. \"Bexar\", \"Bronx\"). Published by TX, NY, FL, CA and MO only; any other requested source refuses a county filter for its leg (disclosed on every row) rather than returning its whole state as if filtered."
5685
5848
  },
5686
5849
  "sinceDate": {
5687
5850
  "type": "string",
5688
- "description": "Optional floor on the notice date. Pending feeds: applications submitted/received on or after this date. Active feeds: licenses issued on or after this date. MUST be YYYY-MM-DD (e.g. 2026-01-15) - any other format is rejected and the run fails without billing, rather than being spliced unverified into a live query. Slash forms are rejected on purpose: 05/06/2026 is ambiguous between US and EU order. California pending applications publish NO date: combining sinceDate with CA pending fails that leg (disclosed on every row) instead of returning an empty result -- use mode \"delta\" to catch new CA applications."
5851
+ "description": "Optional floor on the notice date. Pending feeds: applications submitted/received on or after this date (WA: LCB notification date). Active feeds: licenses issued on or after this date. MUST be YYYY-MM-DD (e.g. 2026-01-15) - any other format is rejected and the run fails without billing, rather than being spliced unverified into a live query. Slash forms are rejected on purpose: 05/06/2026 is ambiguous between US and EU order. California and Connecticut pending applications publish NO date: combining sinceDate with CA or CT pending fails that leg (disclosed on every row) instead of returning an empty result -- use mode \"delta\" to catch new applications there."
5689
5852
  },
5690
5853
  "maxResults": {
5691
5854
  "type": "integer",
5692
- "description": "Total cap on records emitted across all selected states and feeds (each record is one billable result). The cap is split evenly across source legs. Example: 200.",
5855
+ "description": "Total cap on records emitted across all selected states and feeds (each record is one billable result). Each source gets an even share of what is left, so a source that returns fewer rows than its share passes the rest to the next one. Example: 200.",
5693
5856
  "minimum": 1,
5694
5857
  "maximum": 50000
5858
+ },
5859
+ "verifyOpenings": {
5860
+ "type": "boolean",
5861
+ "description": "Texas only, active feed only. When on (default), every issued TX permit that owes the mixed beverage gross receipts tax (MB, RM, N, NE, NB, NT, D, FB, TB) is joined to the Texas Comptroller's monthly receipts filings: opened_verified reads true when a period with receipts > 0 has been filed, false when at least two obligation periods were due and none carries receipts, and null when it is too early to tell or the permit class never files (beer/wine retailers, package stores). Adds first/latest receipts period, trailing-12-month receipts and the permit's rank in its county. Costs a few extra requests per 50 TX rows and no extra billing; set false to skip it. Applied by default if omitted: true."
5695
5862
  }
5696
5863
  }
5697
5864
  }
@@ -6030,7 +6197,7 @@
6030
6197
  },
6031
6198
  "runBudgetSeconds": {
6032
6199
  "type": "integer",
6033
- "description": "Optional. One time budget for the whole run, shared by the live drift checks (at most 90 s of it) and every site. If hydro.nationalmap.gov is slow and the budget runs out, the sites not yet reached are returned as rows with failure_kind \"deadline\" and null counts/flags instead of the run dragging on. Leave empty to use max(240, 90 + 30 x number of assets) seconds. Clamped to 30-3500; the run also always stops before its Apify timeout. Example: 240.",
6200
+ "description": "Optional. One time budget for the whole run, shared by the live drift checks (at most 180 s of it) and every site. If hydro.nationalmap.gov is slow and the budget runs out, the sites not yet reached are returned as rows with failure_kind \"deadline\" and null counts/flags instead of the run dragging on. Leave empty to use max(240, 90 + 30 x number of assets) seconds. Clamped to 30-3500; the run also always stops before its Apify timeout. Example: 240.",
6034
6201
  "minimum": 30,
6035
6202
  "maximum": 3500
6036
6203
  }
@@ -6828,6 +6995,12 @@
6828
6995
  "description": "Maximum number of lead records (one per unique landlord/agent contact) to push to the dataset. Leads are sorted by portfolio size, largest first, before the cap is applied. Example: 200. Applied by default if omitted: 500.",
6829
6996
  "minimum": 1,
6830
6997
  "maximum": 100000
6998
+ },
6999
+ "runBudgetSeconds": {
7000
+ "type": "integer",
7001
+ "description": "Optional wall-clock budget for the whole run. If data.cityofnewyork.us is slow and the budget runs out before every HPD contact batch is fetched, the run returns an honest PARTIAL instead of timing out: every row carries run_deadline_hit=true and portfolio_counts_complete=false, and buildings_count is then a lower bound. Leave empty for no budget (the run takes as long as the source needs). Example: 240.",
7002
+ "minimum": 30,
7003
+ "maximum": 3600
6831
7004
  }
6832
7005
  }
6833
7006
  }
@@ -7158,10 +7331,10 @@
7158
7331
  "slug": "parcel-owner-lookup",
7159
7332
  "actorId": "malonestar/parcel-owner-lookup",
7160
7333
  "title": "Parcel Owner Lookup — Address to Owner & Assessor Record",
7161
- "description": "Turn street addresses into parcel IDs, owner names, mailing addresses, assessed values & coordinates from official assessor rolls (Chicago/Cook County, Philadelphia, NYC) with Census-geocoder fallback. Address-grounding primitive for AI agents, proptech & skip tracing. $0.02/lookup.",
7334
+ "description": "Address to parcel ID, owner name, mailing address & assessed value from official assessor rolls: Chicago, Philadelphia, NYC + NC, NY State, WI, CO, MN, AR, MA, CT, VT statewide, Phoenix, Houston, Cleveland, Nashville & DC. Census fallback elsewhere. $0.02/lookup.",
7162
7335
  "categories": [
7163
7336
  "REAL_ESTATE",
7164
- "DEVELOPER_TOOLS",
7337
+ "BUSINESS",
7165
7338
  "AGENTS"
7166
7339
  ],
7167
7340
  "storeUrl": "https://apify.com/malonestar/parcel-owner-lookup",
@@ -7187,7 +7360,7 @@
7187
7360
  "properties": {
7188
7361
  "addresses": {
7189
7362
  "type": "array",
7190
- "description": "US street addresses to resolve to a parcel + owner record, one per line (e.g. '1060 W Addison St, Chicago, IL'). v1 matches against the Cook County IL (Chicago), Philadelphia PA, and New York City assessment rolls; addresses outside those areas fall back to Census geocoding (lat/lon only). Every input address always yields exactly one output row — unmatched addresses come back with match_confidence 'none'. Example: [\"1060 W Addison St, Chicago, IL\",\"1234 Market St, Philadelphia, PA\",\"350 5th Ave, New York, NY\"].",
7363
+ "description": "US street addresses to resolve to a parcel + owner record, one per line (e.g. '1060 W Addison St, Chicago, IL'). Matched against the Cook County IL (Chicago), Philadelphia PA and New York City rolls, plus (v1.1) statewide rolls for North Carolina, New York State, Wisconsin, Colorado, Minnesota (opt-in counties), Arkansas, Massachusetts, Connecticut and Vermont and county rolls for Maricopa AZ (Phoenix), Harris TX (Houston), Cuyahoga OH (Cleveland), Nashville TN and Washington DC - routed by the Census-geocoded point's county. Addresses elsewhere fall back to Census geocoding (lat/lon only). Every input address yields exactly one output row; see lookup_status on each row. Example: [\"1060 W Addison St, Chicago, IL\",\"1234 Market St, Philadelphia, PA\",\"350 5th Ave, New York, NY\"].",
7191
7364
  "items": {
7192
7365
  "type": "string"
7193
7366
  }
@@ -7197,6 +7370,12 @@
7197
7370
  "description": "Maximum number of addresses to process (one output row per address). Extra addresses beyond this cap are skipped. Example: 10. Applied by default if omitted: 100.",
7198
7371
  "minimum": 1,
7199
7372
  "maximum": 5000
7373
+ },
7374
+ "runBudgetSeconds": {
7375
+ "type": "integer",
7376
+ "description": "Optional wall-clock budget for the whole run. A parcel roll that is slow to answer is cut when the budget runs out: that address comes back lookup_status \"source_unavailable\" (or keeps the answer the house-number query already gave) with lookup_cut_by_deadline: true, instead of the run hanging. Leave empty for no budget. Example: 240.",
7377
+ "minimum": 30,
7378
+ "maximum": 3600
7200
7379
  }
7201
7380
  },
7202
7381
  "required": [
@@ -7442,7 +7621,7 @@
7442
7621
  "slug": "realtor-license-roster-delta",
7443
7622
  "actorId": "malonestar/realtor-license-roster-delta",
7444
7623
  "title": "Realtor License Roster, New Applicants & Broker Change Delta",
7445
- "description": "Real estate licensee lead feeds for recruiters: TREC daily new license applications (future agents), newly licensed agents, and agent-switched-broker (affiliation change) events. TX live via Socrata; FL weekly DBPR roster. Normalized records with license, status, county, sponsoring broker.",
7624
+ "description": "Real estate licensee lead feeds for recruiters: TX daily license applications, newly licensed agents and broker-change (affiliation) events for TX, FL, CT, CO, NY and DE. Official state licensing data, normalized with license, status, sponsoring broker and address.",
7446
7625
  "categories": [
7447
7626
  "LEAD_GENERATION",
7448
7627
  "REAL_ESTATE",
@@ -7471,39 +7650,40 @@
7471
7650
  "properties": {
7472
7651
  "states": {
7473
7652
  "type": "array",
7474
- "description": "REQUIRED. US states to pull. Supported: TX (roster + daily new applications + delta) and FL (weekly roster CSV + delta). Name the states explicitly — there is no default, so a run cannot quietly return Texas when you asked for Florida. Example: [\"TX\"].",
7653
+ "description": "REQUIRED. US states to pull (two-letter codes; full names also accepted). Supported: TX (daily new applications, new licensees, roster, delta), FL (new licensees, weekly roster, delta), CT (new licensees, roster, delta with broker affiliation), CO (new licensees, roster, delta), DE (new licensees, roster, delta), NY (active roster + broker-change delta; NY publishes no issue date). Several states can be combined; maxResults is split across them. Example: [\"TX\"].",
7475
7654
  "items": {
7476
7655
  "type": "string"
7477
7656
  }
7478
7657
  },
7479
7658
  "feed": {
7480
7659
  "type": "string",
7481
- "description": "Which lead feed to produce. new_applicants = TREC daily license applications (future agents, pre-license, TX only). roster = a bounded slice of the current licensee roster. delta = compare against the saved baseline and emit newly_licensed + affiliation_change events (first run only seeds the baseline). Example: \"new_applicants\".",
7660
+ "description": "Which lead feed to produce. new_applicants = TREC daily license applications (future agents, TX only). new_licensees = licences first issued in the last daysBack days (TX, FL, CT, CO, DE). roster = a bounded slice of the current licensee roster. delta = compare against the saved baseline and emit newly_licensed + affiliation_change events (first run only seeds the baseline). Leave empty to use each state's own default (TX new_applicants; FL/CT/CO/DE new_licensees; NY roster). If a state does not publish the requested feed it serves the closest one it does publish and says so on every row (feed_requested / feed_resolution / feed_note) instead of failing. Example: \"new_applicants\".",
7482
7661
  "enum": [
7483
7662
  "new_applicants",
7663
+ "new_licensees",
7484
7664
  "roster",
7485
7665
  "delta"
7486
7666
  ]
7487
7667
  },
7488
7668
  "daysBack": {
7489
7669
  "type": "integer",
7490
- "description": "Look-back window in days for the new_applicants feed. Anchored to the newest date in the applications dataset (not today), so the feed never comes back empty because of publishing lag. Example: 14.",
7670
+ "description": "Look-back window in days for the new_applicants and new_licensees feeds. Anchored to the newest date actually present in the source (not today), so the feed never comes back empty because of publishing lag. Example: 14.",
7491
7671
  "minimum": 0,
7492
7672
  "maximum": 365
7493
7673
  },
7494
7674
  "maxResults": {
7495
7675
  "type": "integer",
7496
- "description": "Maximum number of records to push to the dataset (billing is per result). Example: 200.",
7676
+ "description": "Maximum number of records to push to the dataset (billing is per result). When several states are requested the cap is split across them, and budget an earlier state does not use passes to the next. Example: 200.",
7497
7677
  "minimum": 1,
7498
7678
  "maximum": 50000
7499
7679
  },
7500
7680
  "name": {
7501
7681
  "type": "string",
7502
- "description": "Optional case-insensitive filter on licensee name (TX: full name; applications: last name; FL: licensee name)."
7682
+ "description": "Optional case-insensitive filter on licensee name (TX: full name; TX applications: last name; FL, CT, NY, DE: licensee name; CO: last name)."
7503
7683
  },
7504
7684
  "county": {
7505
7685
  "type": "string",
7506
- "description": "Optional case-insensitive county filter, e.g. \"Travis\" or \"Escambia\". Note: many TX rows have a blank county."
7686
+ "description": "Optional case-insensitive county filter, e.g. \"Travis\" or \"Escambia\". Supported for TX, FL and NY. CT, CO and DE publish no county column: a run that combines a county filter with those states skips them (and says so) rather than returning unfiltered rows."
7507
7687
  },
7508
7688
  "licenseType": {
7509
7689
  "type": "string",
@@ -7511,11 +7691,11 @@
7511
7691
  },
7512
7692
  "status": {
7513
7693
  "type": "string",
7514
- "description": "Optional case-insensitive license-status filter, e.g. \"Active\". Ignored on the TX applications dataset (it has no status column)."
7694
+ "description": "Optional case-insensitive license-status filter, e.g. \"Active\". Ignored on the TX applications dataset (it has no status column). NY publishes active licences only, so a status filter skips NY."
7515
7695
  },
7516
7696
  "flRegions": {
7517
7697
  "type": "array",
7518
- "description": "Florida DBPR region numbers 1-14 to download when states includes FL (each region is one weekly CSV; files are large, so default is region 1 only). Applied by default if omitted: [\"1\"].",
7698
+ "description": "Florida DBPR region numbers 1-14 to download when states includes FL (each region is one weekly CSV of ~4-5 MB). Region 1 is used when empty. Example: [\"1\"].",
7519
7699
  "items": {
7520
7700
  "type": "string"
7521
7701
  }
@@ -8118,12 +8298,12 @@
8118
8298
  {
8119
8299
  "slug": "sos-registry-monitor",
8120
8300
  "actorId": "malonestar/sos-registry-monitor",
8121
- "title": "Secretary of State Business Search & New-Business Monitor API",
8122
- "description": "Business entity search & KYB API across official US Secretary of State registries. Search companies by name and monitor newly formed LLCs and corporations for lead generation. Bulk and date-range capable, sourced from state open-data portals — no anti-bot. Keyless.",
8301
+ "title": "Verified New Business Openings by State & County (SOS + Tax)",
8302
+ "description": "New business leads from official state data: one row per business that just opened, verified across the Secretary of State charter and the sales-tax permit (first-sales date), with county, NAICS, entity type and a three-valued confidence. TX, CO, CT, NY, OR, PA, DE. Keyless, no anti-bot.",
8123
8303
  "categories": [
8304
+ "BUSINESS",
8124
8305
  "LEAD_GENERATION",
8125
- "DEVELOPER_TOOLS",
8126
- "AUTOMATION"
8306
+ "AGENTS"
8127
8307
  ],
8128
8308
  "storeUrl": "https://apify.com/malonestar/sos-registry-monitor",
8129
8309
  "pricing": {
@@ -8148,15 +8328,16 @@
8148
8328
  "properties": {
8149
8329
  "mode": {
8150
8330
  "type": "string",
8151
- "description": "search = look up companies by name. new_businesses = pull recently formed entities (great for fresh B2B leads / monitoring). Example: \"search\".",
8331
+ "description": "openings = ONE row per business that just opened, resolved across every register the state publishes (TX: SOS charter via the franchise-tax roster + sales-tax permit with first-sales date; other states: the SOS registry) with opened_evidence and a three-valued opened_confidence. search = look up companies by name. new_businesses = raw recently formed entities, one row per registry record. Example: \"openings\". Applied by default if omitted: \"search\".",
8152
8332
  "enum": [
8333
+ "openings",
8153
8334
  "search",
8154
8335
  "new_businesses"
8155
8336
  ]
8156
8337
  },
8157
8338
  "states": {
8158
8339
  "type": "array",
8159
- "description": "REQUIRED. State codes to query. Supported: CO, CT, NY, OR, PA, DE (official state open-data business registries). Every requested state is queried independently and its per-state outcome is reported on the row. Name at least one — there is no default. Example: [\"CO\",\"CT\",\"NY\"].",
8340
+ "description": "REQUIRED. State codes to query. Supported: TX, CO, CT, NY, OR, PA, DE (official state open-data registers). Every requested state is queried independently and its per-state outcome is reported on the row. Texas is read from two Comptroller rosters (franchise tax + sales-tax permits); all other states publish one register, so their openings never read better than opened_confidence=low. Name at least one -- there is no default. Example: [\"TX\",\"CO\"].",
8160
8341
  "items": {
8161
8342
  "type": "string"
8162
8343
  }
@@ -8167,7 +8348,7 @@
8167
8348
  },
8168
8349
  "daysBack": {
8169
8350
  "type": "integer",
8170
- "description": "How many days of newly formed entities to pull (used when no explicit since date is given). Each state is filtered on its own registration/formation column -- see date_basis on the output. Note: PA republishes roughly monthly and OR/DE lag by days, so widen this if a short window returns nothing. Example: 30. Applied by default if omitted: 7.",
8351
+ "description": "How many days back to look (new_businesses and openings modes; used when no explicit since date is given). Each register is filtered on its own date column -- see date_basis / opened_evidence on the output. Note: PA republishes roughly monthly and OR/DE lag by days, so widen this if a short window returns nothing. Example: 30. Applied by default if omitted: 7.",
8171
8352
  "minimum": 1,
8172
8353
  "maximum": 365
8173
8354
  },
@@ -8181,9 +8362,15 @@
8181
8362
  "minimum": 1,
8182
8363
  "maximum": 50000
8183
8364
  },
8365
+ "maxResults": {
8366
+ "type": "integer",
8367
+ "description": "Hard ceiling on the number of rows this run may emit and bill, across all states. In openings mode one row = one business; the cap is shared max-min fairly across the requested states (so a Texas run cannot starve Colorado), and within a state rows are ranked by opened_confidence (high first) then newest evidence. Applied once by a single emission planner. Omit it in openings mode and the run caps itself at 200; omit it in search / new_businesses and only maxPerState applies (the pre-2026-09-26 behaviour). Rows carry result_truncated=true when the cap bound. Example: 50.",
8368
+ "minimum": 1,
8369
+ "maximum": 5000
8370
+ },
8184
8371
  "monitor": {
8185
8372
  "type": "boolean",
8186
- "description": "When scheduled, emit only entities not seen in previous runs of the same query -- turns this into a recurring new-business alert. The baseline is kept in a named store and is scoped to the mode, state set and query, so the first run of a new configuration is a full dump by design (delta_baseline_size reports 0). Applied by default if omitted: false."
8373
+ "description": "When scheduled, emit only entities (or, in openings mode, businesses) not seen in previous runs of the same scope -- turns this into a recurring new-business alert. The baseline is kept in a NAMED store scoped to the mode, state set and query, so the first run of a new configuration is a full dump by design (delta_baseline_size reports 0). Applied by default if omitted: false."
8187
8374
  },
8188
8375
  "socrataAppToken": {
8189
8376
  "type": "string",
@@ -8427,6 +8614,90 @@
8427
8614
  ]
8428
8615
  }
8429
8616
  },
8617
+ {
8618
+ "slug": "ucc-lapse-refinance-window",
8619
+ "actorId": "malonestar/ucc-lapse-refinance-window",
8620
+ "title": "UCC Lien Lapse Leads: Refinance Window + Lender Rollup",
8621
+ "description": "UCC lien lapse leads for equipment-finance, MCA and factoring brokers: UCC-1 financing statements lapsing in the next N days (continuations honoured, terminated liens excluded) with debtor, lender and collateral, plus a secured-party filings rollup by quarter. CO, CT, OR open data. Keyless.",
8622
+ "categories": [
8623
+ "BUSINESS",
8624
+ "LEAD_GENERATION",
8625
+ "AGENTS"
8626
+ ],
8627
+ "storeUrl": "https://apify.com/malonestar/ucc-lapse-refinance-window",
8628
+ "pricing": {
8629
+ "model": "PAY_PER_EVENT",
8630
+ "event": "apify-default-dataset-item",
8631
+ "unit": "UCC lapse lead / rollup row",
8632
+ "usdPerUnit": 0.02,
8633
+ "usdPer1000": 20,
8634
+ "tierDiscountsUsdPerUnit": {
8635
+ "FREE": 0.02,
8636
+ "BRONZE": 0.016,
8637
+ "SILVER": 0.014,
8638
+ "GOLD": 0.011,
8639
+ "PLATINUM": 0.011,
8640
+ "DIAMOND": 0.011
8641
+ },
8642
+ "actorStartUsd": null,
8643
+ "since": "2026-09-26T22:00:50.208Z"
8644
+ },
8645
+ "inputSchema": {
8646
+ "type": "object",
8647
+ "properties": {
8648
+ "states": {
8649
+ "type": "array",
8650
+ "description": "REQUIRED. State codes to query. Shipped: CO (full: debtor + secured party + collateral + lapse), CT (full: debtor + secured party + lapse), OR (partial: secured party + lapse, no debtor table published -- rows link to the filing image). NY, PA, DE, TX, WA publish no UCC dataset and are reported as state_coverage=not_available on the run, never as an empty success. Every state is judged independently and its outcome is stamped on every row. Example: [\"CO\"].",
8651
+ "items": {
8652
+ "type": "string"
8653
+ }
8654
+ },
8655
+ "mode": {
8656
+ "type": "string",
8657
+ "description": "lapse-window = one row per UCC-1 financing statement whose CURRENT lapse date (after continuations; terminated liens excluded) falls inside the next windowDays -- the refinance / re-solicit trigger. secured-party-rollup = initial UCC-1 filings per secured party x quarter over the trailing windowDays (who is lending, how much, where). Example: \"lapse-window\".",
8658
+ "enum": [
8659
+ "lapse-window",
8660
+ "secured-party-rollup"
8661
+ ]
8662
+ },
8663
+ "windowDays": {
8664
+ "type": "integer",
8665
+ "description": "lapse-window: liens lapsing between the window start (today, or sinceDate) and start + windowDays. secured-party-rollup: initial filings accepted in the trailing windowDays (or since sinceDate). 1-365. Example: 90.",
8666
+ "minimum": 1,
8667
+ "maximum": 365
8668
+ },
8669
+ "sinceDate": {
8670
+ "type": "string",
8671
+ "description": "Optional. MUST be YYYY-MM-DD (e.g. 2027-01-01); any other format fails the run without billing rather than being spliced unverified into the live query -- slash forms are rejected on purpose because 05/06/2025 is ambiguous. lapse-window: the window starts here instead of today (ask for lapses in a future quarter). secured-party-rollup: count filings accepted on/after this date instead of today - windowDays."
8672
+ },
8673
+ "securedPartyQuery": {
8674
+ "type": "string",
8675
+ "description": "Optional. Keep only rows whose secured party (lender) name contains this text, case-insensitive (e.g. \"Kubota\", \"Wells Fargo\", \"Small Business Administration\"). Applied after each lien is fully assembled, so a lender named only on an amendment still matches."
8676
+ },
8677
+ "debtorCity": {
8678
+ "type": "string",
8679
+ "description": "Optional. Keep only leads whose primary debtor address city equals this (case-insensitive). CO and CT publish debtor cities; OR publishes no debtor table, so an OR leg with this filter is reported as not applicable rather than silently ignored."
8680
+ },
8681
+ "county": {
8682
+ "type": "string",
8683
+ "description": "Optional. None of the shipped portals publishes a county on the filing record; CO carries one only on farm-product collateral records. A state that cannot honour the filter is reported as not applicable on the run, never silently ignored. Prefer debtorCity."
8684
+ },
8685
+ "maxResults": {
8686
+ "type": "integer",
8687
+ "description": "Hard cap on billable rows for the whole run (all states together, soonest lapse first / largest lender first). When more matched, every row carries result_truncated=true. Default 500; prefill 40 so a first free-tier run costs under $1. Example: 40. Applied by default if omitted: 500.",
8688
+ "minimum": 1,
8689
+ "maximum": 5000
8690
+ },
8691
+ "socrataAppToken": {
8692
+ "type": "string",
8693
+ "description": "Optional free Socrata app token to raise rate limits on the state portals."
8694
+ }
8695
+ },
8696
+ "required": [
8697
+ "states"
8698
+ ]
8699
+ }
8700
+ },
8430
8701
  {
8431
8702
  "slug": "uflpa-entity-list-monitor",
8432
8703
  "actorId": "malonestar/uflpa-entity-list-monitor",
@@ -9375,7 +9646,7 @@
9375
9646
  "description": "Aggregate US state WARN Act layoff & closure notices into one normalized schema and track new / updated / removed deltas. Oregon (HECC export) and Texas (Socrata) live and keyless; CA/NY/WA/WI/IL/NJ scaffolded. For RevOps, recruiting, econ research and CRE. No API key.",
9376
9647
  "categories": [
9377
9648
  "LEAD_GENERATION",
9378
- "DEVELOPER_TOOLS",
9649
+ "BUSINESS",
9379
9650
  "AGENTS"
9380
9651
  ],
9381
9652
  "storeUrl": "https://apify.com/malonestar/warn-layoff-aggregator",
package/src/index.js CHANGED
@@ -5,7 +5,8 @@
5
5
  * here; it is read from src/catalog.json at runtime, because a number in a
6
6
  * comment rots the moment a batch ships and nothing fails when it does.
7
7
  *
8
- * Wiring only. All decision logic lives in src/tools.js (pure, offline-tested)
8
+ * Wiring only. All decision logic lives in src/tools.js (pure, offline-tested),
9
+ * src/presets.js (vertical presets: --preset <name> / GOV_DATA_MCP_PRESET, pure)
9
10
  * and src/apify.js (fetch injected). A test asserts this file holds no logic
10
11
  * beyond dispatch.
11
12
  */
@@ -18,14 +19,38 @@ import { dirname, join } from 'node:path';
18
19
 
19
20
  import { createClient, ApifyError } from './apify.js';
20
21
  import { indexCatalog, listTools, searchCatalog, describeTool, resolveCall, formatRunResult, META_TOOLS } from './tools.js';
22
+ import { parseCliArgs, resolvePreset, describePresets, PRESET_ENV } from './presets.js';
21
23
 
22
24
  const here = dirname(fileURLToPath(import.meta.url));
23
25
  const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
24
26
  const catalog = JSON.parse(readFileSync(join(here, 'catalog.json'), 'utf8'));
25
- const index = indexCatalog(catalog);
27
+
28
+ // --preset <name> / GOV_DATA_MCP_PRESET scopes the server to one vertical.
29
+ // Nothing here decides anything: parsing and resolution are pure (presets.js)
30
+ // and an unknown or empty preset stops the process before the transport opens,
31
+ // so a client never connects to a server serving the wrong shelf.
32
+ const cli = parseCliArgs(process.argv.slice(2), process.env);
33
+ if (cli.listPresets) {
34
+ process.stdout.write(JSON.stringify(describePresets(), null, 2) + '\n');
35
+ process.exit(0);
36
+ }
37
+ if (cli.errors.length) {
38
+ console.error(`[gov-data-mcp] ${cli.errors.join('; ')}. Known presets: ${describePresets().map(p => p.name).join(', ')}. Omit --preset (and ${PRESET_ENV}) for the full catalog.`);
39
+ process.exit(2);
40
+ }
41
+ const chosen = resolvePreset(cli.presetName);
42
+ if (!chosen.ok) {
43
+ console.error(`[gov-data-mcp] ${chosen.error}`);
44
+ process.exit(2);
45
+ }
46
+ const index = indexCatalog(catalog, { preset: chosen.preset, presetName: chosen.name });
47
+
48
+ const identity = chosen.preset
49
+ ? { name: chosen.preset.serverName, version: pkg.version, title: chosen.preset.title, description: chosen.preset.description, websiteUrl: 'https://apify.com/malonestar' }
50
+ : { name: 'gov-data-mcp', version: pkg.version };
26
51
 
27
52
  const server = new Server(
28
- { name: 'gov-data-mcp', version: pkg.version },
53
+ identity,
29
54
  { capabilities: { tools: {} } },
30
55
  );
31
56
 
@@ -71,7 +96,8 @@ async function main() {
71
96
  console.error(`[gov-data-mcp] WARNING: featured tools missing from catalog: ${index.missingFeatured.join(', ')}`);
72
97
  }
73
98
  await server.connect(new StdioServerTransport());
74
- console.error(`[gov-data-mcp] ready — ${index.actors.length} government data tools (catalog generated ${catalog.generatedAt})`);
99
+ const scope = index.presetName ? ` [preset ${index.presetName}: ${index.actors.length} of ${index.catalogTotal}]` : '';
100
+ console.error(`[${identity.name}] ready — ${index.actors.length} government data tools${scope} (catalog generated ${catalog.generatedAt})`);
75
101
  }
76
102
 
77
103
  main().catch(err => {
package/src/presets.js ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Vertical presets — the same package, the same catalog, a smaller front door.
3
+ *
4
+ * WHY THIS EXISTS. Agents pick tools badly past about twenty, and the full
5
+ * server already exposes fifteen. A Phase I ESA consultant's agent does not
6
+ * need to weigh a liquor-license feed against a Superfund screen; a sales
7
+ * agent building a CRE lead list does not need wetlands. A preset restricts the
8
+ * WHOLE surface — the named tools AND what search/describe/run can reach — to
9
+ * one buyer's shelf, and announces itself to the client under its own server
10
+ * name and description, so a connector directory can list three products that
11
+ * are, underneath, one npm package with one catalog and one release train.
12
+ *
13
+ * WHY NOT THREE NPM PACKAGES. A first `npm publish` cannot be automated from
14
+ * CI (Trusted Publishing needs the package to already exist), each package
15
+ * would carry its own copy of the catalog to drift, and the Official MCP
16
+ * Registry entry stays singular. One package, one gate, `--preset <name>`.
17
+ *
18
+ * Pure. No I/O. `resolvePreset` and `parseCliArgs` are what index.js calls.
19
+ *
20
+ * Every slug listed here is asserted to exist in the bundled catalog by
21
+ * test/presets.test.js, and the preset count is derived into the README by
22
+ * tools/gen-readme-coverage.cjs — never typed.
23
+ */
24
+
25
+ export const PRESET_ENV = 'GOV_DATA_MCP_PRESET';
26
+
27
+ export const PRESETS = {
28
+ 'phase1-esa': {
29
+ serverName: 'gov-data-mcp-phase1-esa',
30
+ title: 'Phase I ESA — Environmental Due Diligence',
31
+ description: 'US government environmental records for Phase I Environmental Site Assessments and property due diligence: '
32
+ + 'EPA Superfund / RCRA / UST / brownfield records at ASTM E1527-21 search distances, state tank and spill registries, '
33
+ + 'historic land use from FRS SIC/NAICS, USGS historical topographic map coverage, NWI wetlands, Clean Water Act §404 '
34
+ + 'surface water, RCRA corrective-action cleanups, orphaned wells, FEMA hazard risk and NFIP flood-loss history, plus a '
35
+ + 'twenty-layer go / caution / no-go site verdict. Every tool reads the official source directly and reports what it '
36
+ + 'could not check as null, never as clear.',
37
+ tools: [
38
+ 'site-due-diligence-bundle',
39
+ 'epa-contaminated-site-screener',
40
+ 'historic-land-use-sic-contaminant-screener',
41
+ 'state-tank-spill-registry-screener',
42
+ 'usgs-historical-topo-records-review',
43
+ 'epa-rcra-corrective-action-cleanup-monitor',
44
+ 'orphaned-well-proximity-screener',
45
+ 'fws-wetlands-proximity-screener',
46
+ 'nhd-surface-water-404-screener',
47
+ 'fema-nri-county-risk-profile',
48
+ 'nfip-flood-loss-risk-screener',
49
+ ],
50
+ },
51
+ 'cre-leads': {
52
+ serverName: 'gov-data-mcp-cre-leads',
53
+ title: 'CRE & Local Business Leads — Who Just Changed State',
54
+ description: 'Lead-generation feeds built from official state and county registers: new and pending liquor licenses, '
55
+ + 'verified new business openings from Secretary of State and tax rosters, WARN layoff notices, newly licensed real '
56
+ + 'estate agents, licensed childcare providers with contacts, parcel owner of record, absentee-owner lists, '
57
+ + 'distressed-property signal stacks, NYC deed transfers and landlord registries, city business licenses and '
58
+ + 'professional license verification. Delta modes return only what changed since the last run, so a scheduled agent '
59
+ + 'pays for new leads, not the same roster twice.',
60
+ tools: [
61
+ 'liquor-license-new-openings-tracker',
62
+ 'sos-registry-monitor',
63
+ 'warn-layoff-aggregator',
64
+ 'realtor-license-roster-delta',
65
+ 'childcare-provider-leads',
66
+ 'parcel-owner-lookup',
67
+ 'absentee-owner-lead-list-builder',
68
+ 'distressed-property-signal-stacker',
69
+ 'acris-deed-transfer-intel',
70
+ 'nyc-landlord-registry-lead-list',
71
+ 'city-business-license-leads',
72
+ 'license-verifier',
73
+ ],
74
+ },
75
+ 'compliance': {
76
+ serverName: 'gov-data-mcp-compliance',
77
+ title: 'KYB, Sanctions & Financial Compliance Screening',
78
+ description: 'Know-your-business and compliance screening from primary US government registers: company existence '
79
+ + 'across Secretary of State registries, the Trade.gov Consolidated Screening List (OFAC SDN, BIS, State) as a delta, '
80
+ + 'the DHS UFLPA Entity List, state Medicaid and HHS-OIG exclusion lists, professional license verification, GLEIF '
81
+ + 'LEI ownership graphs, FDIC / NCUA institution health, FDIC structure changes, SEC investment-adviser registrations, '
82
+ + 'PCAOB auditor engagements and SEC Regulation CF / A+ offering lifecycles. A screen that could not reach its source '
83
+ + 'fails loudly rather than returning a clean sheet.',
84
+ tools: [
85
+ 'kyb-company-verifier',
86
+ 'consolidated-screening-list-delta',
87
+ 'uflpa-entity-list-monitor',
88
+ 'medicaid-exclusion-screener',
89
+ 'license-verifier',
90
+ 'gleif-ownership-graph',
91
+ 'fdic-ncua-health-rollup',
92
+ 'fdic-structure-change-delta-monitor',
93
+ 'ria-registration-delta-monitor',
94
+ 'pcaob-auditor-engagement-monitor',
95
+ 'reg-cf-lifecycle-monitor',
96
+ 'reg-a-plus-lifecycle-monitor',
97
+ ],
98
+ },
99
+ };
100
+
101
+ export const PRESET_NAMES = Object.keys(PRESETS);
102
+
103
+ /** Agents choose badly past ~20 tools; a preset plus its three meta tools must stay under this. */
104
+ export const MAX_PRESET_TOOLS = 15;
105
+
106
+ /**
107
+ * Resolve a preset name to its definition, or an explicit error. Never guesses:
108
+ * "phase1" does not become "phase1-esa", because a server that silently started
109
+ * with the wrong shelf would be the quiet failure this project exists to avoid.
110
+ */
111
+ export function resolvePreset(name) {
112
+ if (name === undefined || name === null || String(name).trim() === '') return { ok: true, preset: null, name: null };
113
+ const key = String(name).trim();
114
+ const preset = PRESETS[key];
115
+ if (!preset) {
116
+ return {
117
+ ok: false,
118
+ error: `Unknown preset "${key}". Known presets: ${PRESET_NAMES.join(', ')}. `
119
+ + `Pass --preset <name> or set ${PRESET_ENV}; omit both for the full catalog.`,
120
+ };
121
+ }
122
+ return { ok: true, preset, name: key };
123
+ }
124
+
125
+ /**
126
+ * Parse the CLI + environment into { presetName, listPresets }. The flag wins
127
+ * over the environment variable, and an empty value is an error rather than
128
+ * "no preset" — `--preset` with nothing after it is a typo, not a choice.
129
+ */
130
+ export function parseCliArgs(argv, env) {
131
+ const args = Array.isArray(argv) ? argv : [];
132
+ const out = { presetName: null, listPresets: false, errors: [] };
133
+ for (let i = 0; i < args.length; i++) {
134
+ const a = args[i];
135
+ if (a === '--list-presets') { out.listPresets = true; continue; }
136
+ if (a === '--preset') {
137
+ const v = args[i + 1];
138
+ if (v === undefined || v.startsWith('--')) { out.errors.push('--preset requires a value'); continue; }
139
+ out.presetName = v; i++; continue;
140
+ }
141
+ if (a.startsWith('--preset=')) {
142
+ const v = a.slice('--preset='.length);
143
+ if (!v) { out.errors.push('--preset requires a value'); continue; }
144
+ out.presetName = v; continue;
145
+ }
146
+ }
147
+ if (out.presetName === null && env && typeof env[PRESET_ENV] === 'string' && env[PRESET_ENV].trim() !== '') {
148
+ out.presetName = env[PRESET_ENV].trim();
149
+ }
150
+ return out;
151
+ }
152
+
153
+ /** One line per preset, for --list-presets and for the README generator. */
154
+ export function describePresets() {
155
+ return PRESET_NAMES.map(n => ({
156
+ name: n,
157
+ serverName: PRESETS[n].serverName,
158
+ title: PRESETS[n].title,
159
+ toolCount: PRESETS[n].tools.length,
160
+ tools: [...PRESETS[n].tools],
161
+ }));
162
+ }
package/src/tools.js CHANGED
@@ -59,11 +59,37 @@ export function toolNameFor(slug) {
59
59
  return slug;
60
60
  }
61
61
 
62
- export function indexCatalog(catalog) {
63
- const actors = catalog.actors || [];
62
+ /**
63
+ * Build the tool index. With no options this is the full catalog fronted by
64
+ * FEATURED — byte-identical to every release before presets existed.
65
+ *
66
+ * With a preset, the WHOLE index is restricted: `actors` (what search,
67
+ * describe and run can reach) is filtered to the preset's tools, and
68
+ * `featured` becomes the preset's own list. A preset tool that is not in the
69
+ * catalog is a hard error, not a warning — a scoped server advertising a tool
70
+ * that does not exist is precisely the defect a stderr line would hide.
71
+ */
72
+ export function indexCatalog(catalog, options = {}) {
73
+ const all = catalog.actors || [];
74
+ const preset = options.preset || null;
75
+ const presetName = options.presetName || null;
76
+ if (preset) {
77
+ const missing = preset.tools.filter(s => !all.some(a => a.slug === s));
78
+ if (missing.length) {
79
+ throw new Error(`preset "${presetName}" names tools that are not in the bundled catalog: ${missing.join(', ')}. `
80
+ + 'Regenerate the catalog or fix the preset before starting a server that would advertise them.');
81
+ }
82
+ }
83
+ const allSlugs = new Set(all.map(a => a.slug));
84
+ const featured = preset ? [...preset.tools] : [...FEATURED];
85
+ const keep = preset ? new Set(preset.tools) : null;
86
+ const actors = keep ? all.filter(a => keep.has(a.slug)) : all;
64
87
  const bySlug = new Map(actors.map(a => [a.slug, a]));
65
- const missingFeatured = FEATURED.filter(s => !bySlug.has(s));
66
- return { actors, bySlug, missingFeatured };
88
+ const missingFeatured = featured.filter(s => !bySlug.has(s));
89
+ const scopeNote = preset
90
+ ? ` This server is scoped to the "${presetName}" preset (${actors.length} of ${all.length} catalog tools); start gov-data-mcp without --preset for the full catalog.`
91
+ : '';
92
+ return { actors, bySlug, missingFeatured, featured, presetName, scopeNote, catalogTotal: all.length, allSlugs };
67
93
  }
68
94
 
69
95
  function truncate(s, n) {
@@ -151,10 +177,26 @@ export const ROUTING = {
151
177
  + 'It is NOT a property contamination screen — for soil and groundwater records at a site use epa-contaminated-site-screener.',
152
178
  };
153
179
 
180
+ /**
181
+ * A routing note is only shown when every tool it points at is reachable on
182
+ * THIS server. Under a preset, a note that sends the agent to a tool outside
183
+ * the preset would be a dead end dressed as advice, so the note is dropped
184
+ * rather than edited — a half-note that names a missing tool is worse than none.
185
+ */
186
+ export function routingNoteFor(index, slug) {
187
+ const note = ROUTING[slug];
188
+ if (!note) return '';
189
+ // Only tokens that are catalog slugs count as references; hyphenated prose is not a tool.
190
+ const isSlug = (r) => index.allSlugs ? index.allSlugs.has(r) : index.bySlug.has(r);
191
+ const referenced = (note.match(/[a-z0-9]+(?:-[a-z0-9]+){2,}/g) || []).filter(r => r !== slug && isSlug(r));
192
+ return referenced.every(r => index.bySlug.has(r)) ? ` ${note}` : '';
193
+ }
194
+
154
195
  export function featuredToolDefinitions(index) {
155
- return FEATURED.filter(s => index.bySlug.has(s)).map(slug => {
196
+ const featured = index.featured || FEATURED;
197
+ return featured.filter(s => index.bySlug.has(s)).map(slug => {
156
198
  const a = index.bySlug.get(slug);
157
- const routing = ROUTING[slug] ? ` ${ROUTING[slug]}` : '';
199
+ const routing = routingNoteFor(index, slug);
158
200
  return {
159
201
  name: toolNameFor(slug),
160
202
  description: `${a.title}. ${truncate(a.description, 400)}${routing} Reads live from the official government source. ${costNote(a.pricing)} Store page: ${a.storeUrl}`,
@@ -169,7 +211,7 @@ export function metaToolDefinitions(index) {
169
211
  return [
170
212
  {
171
213
  name: META_TOOLS.SEARCH,
172
- description: `Search the full catalog of ${n} US government data tools by keyword, agency, or topic (e.g. "wetlands", "FDIC", "flood", "drone airspace", "business licenses"). Returns matching tool names with descriptions. Use this first when the task is not covered by one of the dedicated tools above. FREE: reads a catalog bundled with this server — no network call, no run, nothing charged.`,
214
+ description: `Search the full catalog of ${n} US government data tools by keyword, agency, or topic (e.g. "wetlands", "FDIC", "flood", "drone airspace", "business licenses"). Returns matching tool names with descriptions. Use this first when the task is not covered by one of the dedicated tools above.${index.scopeNote || ''} FREE: reads a catalog bundled with this server — no network call, no run, nothing charged.`,
173
215
  annotations: { title: 'Search the government data catalog', ...ANNOTATIONS.CATALOG_LOCAL },
174
216
  inputSchema: {
175
217
  type: 'object',
@@ -246,7 +288,7 @@ export function describeTool(index, name) {
246
288
  const near = searchCatalog(index, String(name || '').replace(/[-_]/g, ' '), 5).map(r => r.tool);
247
289
  return {
248
290
  ok: false,
249
- error: `No tool named "${name}" in this catalog.${near.length ? ` Closest matches: ${near.join(', ')}.` : ''} Use ${META_TOOLS.SEARCH} to find one. This is a lookup miss, not a statement about the underlying data.`,
291
+ error: `No tool named "${name}" in this catalog.${near.length ? ` Closest matches: ${near.join(', ')}.` : ''} Use ${META_TOOLS.SEARCH} to find one. This is a lookup miss, not a statement about the underlying data.${index.scopeNote || ''}`,
250
292
  };
251
293
  }
252
294
  return {
@@ -271,7 +313,7 @@ export function resolveCall(index, toolName, args) {
271
313
  if (!slug) return { ok: false, error: `${META_TOOLS.RUN} requires a "tool" argument naming which catalog tool to run.` };
272
314
  if (!index.bySlug.has(slug)) {
273
315
  const near = searchCatalog(index, String(slug).replace(/[-_]/g, ' '), 5).map(r => r.tool);
274
- return { ok: false, error: `No tool named "${slug}" in this catalog.${near.length ? ` Closest matches: ${near.join(', ')}.` : ''}` };
316
+ return { ok: false, error: `No tool named "${slug}" in this catalog.${near.length ? ` Closest matches: ${near.join(', ')}.` : ''}${index.scopeNote || ''}` };
275
317
  }
276
318
  return { ok: true, slug, input: (args && args.input) || {}, maxItems: args && args.maxItems };
277
319
  }