@cliwant/mcp-sam-gov 1.3.0 → 1.4.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.
Files changed (83) hide show
  1. package/README.ja.md +15 -7
  2. package/README.ko.md +15 -7
  3. package/README.md +48 -8
  4. package/dist/bea.d.ts +1 -1
  5. package/dist/bea.js +1 -1
  6. package/dist/census-economic.d.ts +1 -1
  7. package/dist/census-economic.d.ts.map +1 -1
  8. package/dist/census-economic.js +12 -6
  9. package/dist/census-economic.js.map +1 -1
  10. package/dist/cms-facility.d.ts +112 -0
  11. package/dist/cms-facility.d.ts.map +1 -0
  12. package/dist/cms-facility.js +311 -0
  13. package/dist/cms-facility.js.map +1 -0
  14. package/dist/cms-hospital.d.ts +105 -0
  15. package/dist/cms-hospital.d.ts.map +1 -0
  16. package/dist/cms-hospital.js +290 -0
  17. package/dist/cms-hospital.js.map +1 -0
  18. package/dist/cms-supplier.d.ts +133 -0
  19. package/dist/cms-supplier.d.ts.map +1 -0
  20. package/dist/cms-supplier.js +414 -0
  21. package/dist/cms-supplier.js.map +1 -0
  22. package/dist/cms-utilization.d.ts +113 -0
  23. package/dist/cms-utilization.d.ts.map +1 -0
  24. package/dist/cms-utilization.js +328 -0
  25. package/dist/cms-utilization.js.map +1 -0
  26. package/dist/courtlistener.d.ts +115 -0
  27. package/dist/courtlistener.d.ts.map +1 -0
  28. package/dist/courtlistener.js +398 -0
  29. package/dist/courtlistener.js.map +1 -0
  30. package/dist/cpsc.d.ts +81 -0
  31. package/dist/cpsc.d.ts.map +1 -0
  32. package/dist/cpsc.js +283 -0
  33. package/dist/cpsc.js.map +1 -0
  34. package/dist/dol.d.ts +2 -2
  35. package/dist/dol.js +5 -5
  36. package/dist/dol.js.map +1 -1
  37. package/dist/epa-envirofacts.d.ts +97 -0
  38. package/dist/epa-envirofacts.d.ts.map +1 -0
  39. package/dist/epa-envirofacts.js +292 -0
  40. package/dist/epa-envirofacts.js.map +1 -0
  41. package/dist/fred.d.ts +1 -1
  42. package/dist/fred.js +1 -1
  43. package/dist/keys.d.ts +6 -5
  44. package/dist/keys.d.ts.map +1 -1
  45. package/dist/keys.js +25 -6
  46. package/dist/keys.js.map +1 -1
  47. package/dist/nhtsa.d.ts +91 -0
  48. package/dist/nhtsa.d.ts.map +1 -0
  49. package/dist/nhtsa.js +263 -0
  50. package/dist/nhtsa.js.map +1 -0
  51. package/dist/nonprofit.d.ts +116 -0
  52. package/dist/nonprofit.d.ts.map +1 -0
  53. package/dist/nonprofit.js +342 -0
  54. package/dist/nonprofit.js.map +1 -0
  55. package/dist/openfda-device.d.ts +85 -0
  56. package/dist/openfda-device.d.ts.map +1 -0
  57. package/dist/openfda-device.js +277 -0
  58. package/dist/openfda-device.js.map +1 -0
  59. package/dist/openfda.d.ts +133 -0
  60. package/dist/openfda.d.ts.map +1 -0
  61. package/dist/openfda.js +402 -0
  62. package/dist/openfda.js.map +1 -0
  63. package/dist/server.d.ts.map +1 -1
  64. package/dist/server.js +666 -7
  65. package/dist/server.js.map +1 -1
  66. package/package.json +2 -1
  67. package/src/bea.ts +1 -1
  68. package/src/census-economic.ts +12 -6
  69. package/src/cms-facility.ts +379 -0
  70. package/src/cms-hospital.ts +344 -0
  71. package/src/cms-supplier.ts +527 -0
  72. package/src/cms-utilization.ts +389 -0
  73. package/src/courtlistener.ts +465 -0
  74. package/src/cpsc.ts +333 -0
  75. package/src/dol.ts +5 -5
  76. package/src/epa-envirofacts.ts +342 -0
  77. package/src/fred.ts +1 -1
  78. package/src/keys.ts +27 -6
  79. package/src/nhtsa.ts +352 -0
  80. package/src/nonprofit.ts +460 -0
  81. package/src/openfda-device.ts +356 -0
  82. package/src/openfda.ts +495 -0
  83. package/src/server.ts +765 -7
package/dist/server.js CHANGED
@@ -45,11 +45,18 @@ import * as nsf from "./nsf.js";
45
45
  import * as clinicaltrials from "./clinicaltrials.js";
46
46
  import * as census from "./census.js";
47
47
  import * as censusEconomic from "./census-economic.js";
48
+ import * as epaEnvirofacts from "./epa-envirofacts.js";
49
+ import * as cmsUtilization from "./cms-utilization.js";
50
+ import * as cmsHospital from "./cms-hospital.js";
51
+ import * as cmsFacility from "./cms-facility.js";
52
+ import * as cmsSupplier from "./cms-supplier.js";
48
53
  import * as fred from "./fred.js";
49
54
  import * as bea from "./bea.js";
50
55
  import * as gsaPerdiem from "./gsa-perdiem.js";
51
56
  import * as dol from "./dol.js";
52
57
  import * as lda from "./lda.js";
58
+ import * as courtlistener from "./courtlistener.js";
59
+ import * as nonprofit from "./nonprofit.js";
53
60
  import * as fema from "./fema.js";
54
61
  import * as fdic from "./fdic.js";
55
62
  import * as bls from "./bls.js";
@@ -59,6 +66,10 @@ import * as nppes from "./nppes.js";
59
66
  import * as cms from "./cms.js";
60
67
  import * as fac from "./fac.js";
61
68
  import * as usitc from "./usitc.js";
69
+ import * as openfda from "./openfda.js";
70
+ import * as openfdaDevice from "./openfda-device.js";
71
+ import * as nhtsa from "./nhtsa.js";
72
+ import * as cpsc from "./cpsc.js";
62
73
  import { fetchAttachmentText } from "./attachments.js";
63
74
  import * as keys from "./keys.js";
64
75
  import { toToolError, ToolErrorCarrier, errorFromResponse } from "./errors.js";
@@ -68,7 +79,7 @@ import { realpathSync } from "node:fs";
68
79
  const SERVER_NAME = "mcp-sam-gov";
69
80
  // Kept in lockstep with package.json / manifest.json / server.json.
70
81
  // Keep in sync with package.json "version" (asserted at release; see CHANGELOG).
71
- const SERVER_VERSION = "1.3.0";
82
+ const SERVER_VERSION = "1.4.0";
72
83
  // ─── Tool input schemas (Zod) ────────────────────────────────────
73
84
  const SamSearchInput = z.object({
74
85
  query: z.string().optional().describe("Free-text title query"),
@@ -2824,6 +2835,248 @@ const CensusBusinessPatternsInput = z.object({
2824
2835
  .optional()
2825
2836
  .describe("OPTIONAL client-side top-N cap on the returned rows. CBP has NO server-side pagination, so this slices AFTER the full set is fetched and DISCLOSES the omission (totalAvailable stays the full count). Omit to return every matching row."),
2826
2837
  });
2838
+ // ─── EPA Envirofacts TRI facilities (ADR-0059) — keyless, PATH-segment SSRF ──
2839
+ // data.epa.gov /efservice/tri_facility. Two requests: a count sub-query for the
2840
+ // EXACT total (P1) + the data slice. All user values ride as PATH SEGMENTS, so each
2841
+ // is charclass-validated + encodeURIComponent-encoded (the load-bearing SSRF guard).
2842
+ const EpaTriFacilitiesInput = z
2843
+ .object({
2844
+ state: z
2845
+ .string()
2846
+ .regex(/^[A-Za-z]{2}$/)
2847
+ .optional()
2848
+ .describe("A 2-letter US state/territory code, e.g. 'VA', 'CA', 'PR' (→ state_abbr; case-insensitive). Provide at least this OR `facilityName`. Validated ^[A-Za-z]{2}$ (it rides in the request path)."),
2849
+ facilityName: z
2850
+ .string()
2851
+ .min(1)
2852
+ .max(100)
2853
+ .regex(/^[A-Za-z0-9 &.\-]+$/)
2854
+ .optional()
2855
+ .describe("A partial facility-name match (→ facility_name/CONTAINING/…; case-insensitive), e.g. 'chemical', 'boeing'. Provide at least this OR `state`. Allowed: letters/digits/space/& - . (≤100 chars); '/' and '..' rejected (path-injection guard)."),
2856
+ county: z
2857
+ .string()
2858
+ .min(1)
2859
+ .max(100)
2860
+ .regex(/^[A-Za-z0-9 &.\-]+$/)
2861
+ .optional()
2862
+ .describe("A partial county-name match (→ county_name/CONTAINING/…), e.g. 'FAIRFAX'. Optional additional filter; same charclass as facilityName."),
2863
+ limit: z
2864
+ .number()
2865
+ .int()
2866
+ .min(1)
2867
+ .max(100)
2868
+ .optional()
2869
+ .describe("Max facilities to return (1–100, default 25). Offset-paginated."),
2870
+ offset: z
2871
+ .number()
2872
+ .int()
2873
+ .min(0)
2874
+ .optional()
2875
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
2876
+ })
2877
+ .refine((v) => v.state !== undefined || v.facilityName !== undefined, {
2878
+ message: "Provide at least `state` or `facilityName` (an all-empty query would scan the whole national TRI table and is refused).",
2879
+ path: ["state"],
2880
+ });
2881
+ // ─── CMS Medicare provider-service utilization (ADR-0061) — keyless, two-request ──
2882
+ // data.cms.gov /data-api/v1/dataset/{uuid}. Two requests: a stats count sub-query
2883
+ // for the EXACT total (P1 — found_rows) + the data slice (a bare JSON array). Filter
2884
+ // VALUES ride via URLSearchParams (bracket key + value encoded). REQUIRE npi OR
2885
+ // state (the 9.78M-row table is never scanned unscoped).
2886
+ const CmsMedicareProviderServicesInput = z
2887
+ .object({
2888
+ npi: z
2889
+ .string()
2890
+ .regex(/^\d{10}$/)
2891
+ .optional()
2892
+ .describe("A 10-digit National Provider Identifier (→ Rndrng_NPI), e.g. '1003000126'. Provide at least this OR `state`. Validated ^\\d{10}$."),
2893
+ state: z
2894
+ .string()
2895
+ .regex(/^[A-Za-z]{2}$/)
2896
+ .optional()
2897
+ .describe("A 2-letter US state/territory code (→ Rndrng_Prvdr_State_Abrvtn), e.g. 'VA', 'CA'. Provide at least this OR `npi`. Validated ^[A-Za-z]{2}$."),
2898
+ providerType: z
2899
+ .string()
2900
+ .min(1)
2901
+ .max(100)
2902
+ .regex(/^[A-Za-z0-9 &.,()/'-]+$/)
2903
+ .optional()
2904
+ .describe("An optional specialty filter matching the CMS provider type EXACTLY (→ Rndrng_Prvdr_Type), e.g. 'Family Practice', 'Physical Therapist in Private Practice'. Allowed: letters/digits/space/& . , ( ) / ' - (≤100 chars)."),
2905
+ hcpcsCode: z
2906
+ .string()
2907
+ .regex(/^[A-Za-z0-9]{1,10}$/)
2908
+ .optional()
2909
+ .describe("An optional HCPCS/CPT service code filter (→ HCPCS_Cd), e.g. '97110', 'G0463'. Validated ^[A-Za-z0-9]{1,10}$."),
2910
+ size: z
2911
+ .number()
2912
+ .int()
2913
+ .min(1)
2914
+ .max(100)
2915
+ .optional()
2916
+ .describe("Max provider-service rows to return (1–100, default 25). Offset-paginated."),
2917
+ offset: z
2918
+ .number()
2919
+ .int()
2920
+ .min(0)
2921
+ .optional()
2922
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
2923
+ })
2924
+ .refine((v) => v.npi !== undefined || v.state !== undefined, {
2925
+ message: "Provide at least `npi` or `state` (an all-empty query would scan the entire 9.78M-row Medicare utilization table and is refused; providerType/hcpcsCode alone are not enough to scope).",
2926
+ path: ["npi"],
2927
+ });
2928
+ // ─── CMS Hospital Compare "Hospital General Information" (ADR-0062) — keyless ──
2929
+ // data.cms.gov /provider-data/api/1/datastore/query/{datasetId}/0. A SINGLE request:
2930
+ // the response's top-level `count` is the EXACT per-filter total (P1). Filters ride
2931
+ // as DKAN conditions[] triples via URLSearchParams (bracket key + value encoded).
2932
+ // REQUIRE state OR facilityName (the ~5,432-hospital table is never scanned unscoped).
2933
+ const CmsHospitalCompareInput = z
2934
+ .object({
2935
+ state: z
2936
+ .string()
2937
+ .regex(/^[A-Za-z]{2}$/)
2938
+ .optional()
2939
+ .describe("A 2-letter US state/territory code (→ state, EXACT match), e.g. 'VA', 'CA'. Provide at least this OR `facilityName`. Validated ^[A-Za-z]{2}$."),
2940
+ facilityName: z
2941
+ .string()
2942
+ .min(1)
2943
+ .max(100)
2944
+ .regex(/^[A-Za-z0-9 &.,()/'-]+$/)
2945
+ .optional()
2946
+ .describe("A hospital-name fragment (→ facility_name, case-insensitive SUBSTRING/contains match), e.g. 'children'. Provide at least this OR `state`. Allowed: letters/digits/space/& . , ( ) / ' - (≤100 chars)."),
2947
+ hospitalType: z
2948
+ .string()
2949
+ .min(1)
2950
+ .max(100)
2951
+ .regex(/^[A-Za-z0-9 &.,()/'-]+$/)
2952
+ .optional()
2953
+ .describe("An optional hospital-type filter (→ hospital_type, case-insensitive SUBSTRING/contains match), e.g. 'Acute', 'Critical Access'. Allowed: letters/digits/space/& . , ( ) / ' - (≤100 chars)."),
2954
+ size: z
2955
+ .number()
2956
+ .int()
2957
+ .min(1)
2958
+ .max(100)
2959
+ .optional()
2960
+ .describe("Max hospital rows to return (1–100, default 25). Offset-paginated."),
2961
+ offset: z
2962
+ .number()
2963
+ .int()
2964
+ .min(0)
2965
+ .optional()
2966
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
2967
+ })
2968
+ .refine((v) => v.state !== undefined || v.facilityName !== undefined, {
2969
+ message: "Provide at least `state` or `facilityName` (an all-empty query would scan the entire ~5,432-hospital table and is refused; hospitalType alone is not enough to scope).",
2970
+ path: ["state"],
2971
+ });
2972
+ // ─── CMS Facility Directory (data.cms.gov provider-data, ADR-0063) — KEYLESS ──
2973
+ // A four-dataset facility directory generalizing cms_hospital_compare beyond
2974
+ // hospitals. `facilityType` is a Zod ENUM that indexes a MODULE-CONSTANT map to a
2975
+ // VETTED dataset id (nursing_home → 4pq5-n9py, home_health → 6jpm-sxkc, hospice →
2976
+ // yc9t-dgbk, dialysis → 23ew-n7w9) — the user value never enters the URL path. A
2977
+ // SINGLE request: the response's top-level `count` is the EXACT per-filter total
2978
+ // (P1). Filters ride as DKAN conditions[] triples via URLSearchParams. name/address/
2979
+ // ownership columns vary per dataset → coalesced (null if none).
2980
+ const CmsFacilityDirectoryInput = z.object({
2981
+ facilityType: z
2982
+ .enum(["nursing_home", "home_health", "hospice", "dialysis"])
2983
+ .describe("REQUIRED — which CMS provider-data dataset to search: 'nursing_home' (~14,695), 'home_health' (~12,460), 'hospice' (~6,852), or 'dialysis' (~7,490). Selects the dataset id via a constant map (the value never enters the URL path)."),
2984
+ state: z
2985
+ .string()
2986
+ .regex(/^[A-Za-z]{2}$/)
2987
+ .optional()
2988
+ .describe("An optional 2-letter US state/territory code (→ state, EXACT match), e.g. 'VA', 'TX'. Validated ^[A-Za-z]{2}$."),
2989
+ facilityName: z
2990
+ .string()
2991
+ .min(1)
2992
+ .max(100)
2993
+ .regex(/^[A-Za-z0-9 &.,()/'-]+$/)
2994
+ .optional()
2995
+ .describe("An optional facility-name fragment (case-insensitive SUBSTRING/contains match against the dataset's primary-name column). Allowed: letters/digits/space/& . , ( ) / ' - (≤100 chars)."),
2996
+ size: z
2997
+ .number()
2998
+ .int()
2999
+ .min(1)
3000
+ .max(100)
3001
+ .optional()
3002
+ .describe("Max facility rows to return (1–100, default 25). Offset-paginated."),
3003
+ offset: z
3004
+ .number()
3005
+ .int()
3006
+ .min(0)
3007
+ .optional()
3008
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
3009
+ });
3010
+ // ─── CMS DMEPOS by Supplier (data.cms.gov data-API, ADR-0064) — KEYLESS ──
3011
+ // SAME host/endpoint/two-request-stats-count pattern as cms_medicare_provider_services.
3012
+ // REQUIRE npi OR state (the supplier table is never scanned unscoped). Filter VALUES
3013
+ // ride as URLSearchParams filter[Col]=Val (bracket key + value encoded — the SSRF guard).
3014
+ const CmsDmeposSuppliersInput = z
3015
+ .object({
3016
+ npi: z
3017
+ .string()
3018
+ .regex(/^\d{10}$/)
3019
+ .optional()
3020
+ .describe("A 10-digit supplier National Provider Identifier (→ Suplr_NPI), e.g. '1003000126'. Provide at least this OR `state`. Validated ^\\d{10}$."),
3021
+ state: z
3022
+ .string()
3023
+ .regex(/^[A-Za-z]{2}$/)
3024
+ .optional()
3025
+ .describe("A 2-letter US state/territory code (→ Suplr_Prvdr_State_Abrvtn), e.g. 'VA', 'CA'. Provide at least this OR `npi`. Validated ^[A-Za-z]{2}$."),
3026
+ size: z
3027
+ .number()
3028
+ .int()
3029
+ .min(1)
3030
+ .max(100)
3031
+ .optional()
3032
+ .describe("Max supplier rows to return (1–100, default 25). Offset-paginated."),
3033
+ offset: z
3034
+ .number()
3035
+ .int()
3036
+ .min(0)
3037
+ .optional()
3038
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
3039
+ })
3040
+ .refine((v) => v.npi !== undefined || v.state !== undefined, {
3041
+ message: "Provide at least `npi` or `state` (an all-empty query would scan the entire DMEPOS supplier table and is refused).",
3042
+ path: ["npi"],
3043
+ });
3044
+ // ─── CMS Revoked Medicare Providers & Suppliers (data.cms.gov data-API, ADR-0064) ──
3045
+ // KEYLESS. A legally-published revocation/exclusion register (~7,059 rows) — the same
3046
+ // vetting class as the OFAC / SAM exclusion lists. ALL filters optional (small table —
3047
+ // pagination is fine unfiltered). SAME two-request stats-count P1 pattern.
3048
+ const CmsRevokedProvidersInput = z.object({
3049
+ npi: z
3050
+ .string()
3051
+ .regex(/^\d{10}$/)
3052
+ .optional()
3053
+ .describe("An optional 10-digit National Provider Identifier (→ NPI), e.g. '1003000126'. Validated ^\\d{10}$."),
3054
+ state: z
3055
+ .string()
3056
+ .regex(/^[A-Za-z]{2}$/)
3057
+ .optional()
3058
+ .describe("An optional 2-letter US state/territory code (→ STATE_CD, EXACT match), e.g. 'FL', 'CA'. Validated ^[A-Za-z]{2}$."),
3059
+ lastName: z
3060
+ .string()
3061
+ .min(1)
3062
+ .max(100)
3063
+ .regex(/^[A-Za-z0-9 .,'-]+$/)
3064
+ .optional()
3065
+ .describe("An optional last-name filter (→ LAST_NAME, EXACT match). Allowed: letters/digits/space/. , ' - (≤100 chars)."),
3066
+ size: z
3067
+ .number()
3068
+ .int()
3069
+ .min(1)
3070
+ .max(100)
3071
+ .optional()
3072
+ .describe("Max revocation rows to return (1–100, default 25). Offset-paginated."),
3073
+ offset: z
3074
+ .number()
3075
+ .int()
3076
+ .min(0)
3077
+ .optional()
3078
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
3079
+ });
2827
3080
  // ─── FRED (Federal Reserve Economic Data) — the SECOND key-required source ──
2828
3081
  // ADR-0048. Macro context (GDP/CPI/rates/unemployment/PPI). REQUIRES a free
2829
3082
  // FRED_API_KEY; without it both tools throw an honest config error (the other 112
@@ -2880,6 +3133,160 @@ const FredSeriesObservationsInput = z.object({
2880
3133
  .optional()
2881
3134
  .describe("Observation date order: 'asc' (oldest first, FRED default) or 'desc' (newest first)."),
2882
3135
  });
3136
+ // ─── openFDA recall/enforcement (api.fda.gov) — KEYLESS + OPTIONAL rate-limit key ──
3137
+ // ADR-0054. Drug/device/food recall enforcement records. Structured filters ONLY
3138
+ // (no raw Lucene passthrough — injection-safe); the tool assembles the openFDA
3139
+ // `search=` string with proper escaping. totalAvailable = meta.results.total (P1);
3140
+ // a no-match query (openFDA HTTP 404 NOT_FOUND) ⇒ an honest empty (P2). An OPTIONAL
3141
+ // OPENFDA_API_KEY only raises the rate limit (keyless works ~1000/day).
3142
+ const OpenfdaEnforcementInput = z.object({
3143
+ category: z
3144
+ .enum(["drug", "device", "food"])
3145
+ .optional()
3146
+ .describe("The recall category (default 'drug'): 'drug', 'device', or 'food'. Selects the openFDA /{category}/enforcement endpoint."),
3147
+ firm: z
3148
+ .string()
3149
+ .min(1)
3150
+ .optional()
3151
+ .describe("Recalling firm name filter (→ recalling_firm), e.g. 'pfizer'. Matched as an escaped Lucene phrase."),
3152
+ product: z
3153
+ .string()
3154
+ .min(1)
3155
+ .optional()
3156
+ .describe("Product description filter (→ product_description), e.g. 'insulin'. Matched as an escaped Lucene phrase."),
3157
+ reason: z
3158
+ .string()
3159
+ .min(1)
3160
+ .optional()
3161
+ .describe("Reason-for-recall filter (→ reason_for_recall), e.g. 'contamination'. Matched as an escaped Lucene phrase."),
3162
+ classification: z
3163
+ .enum(["Class I", "Class II", "Class III"])
3164
+ .optional()
3165
+ .describe("FDA recall classification filter: 'Class I' (most serious), 'Class II', or 'Class III'."),
3166
+ status: z
3167
+ .string()
3168
+ .min(1)
3169
+ .optional()
3170
+ .describe("Recall status filter (→ status), e.g. 'Ongoing', 'Terminated', 'Completed'."),
3171
+ state: z
3172
+ .string()
3173
+ .regex(/^[A-Za-z]{2}$/)
3174
+ .optional()
3175
+ .describe("2-letter US state/territory postal code filter (→ state), e.g. 'CA'. Validated ^[A-Za-z]{2}$."),
3176
+ limit: z
3177
+ .number()
3178
+ .int()
3179
+ .min(1)
3180
+ .max(100)
3181
+ .optional()
3182
+ .describe("Max recall records to return (default 25, max 100). Offset-paginated via skip."),
3183
+ skip: z
3184
+ .number()
3185
+ .int()
3186
+ .min(0)
3187
+ .optional()
3188
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
3189
+ });
3190
+ // ─── openFDA 510(k) device clearances (api.fda.gov) — KEYLESS + OPTIONAL rate-limit key ──
3191
+ // ADR-0056. FDA premarket-notification (510(k)) device clearances — SAME source/envelope/
3192
+ // crux as openfda_enforcement (structured filters ONLY — the tool assembles + escapes the
3193
+ // search= string, injection-safe). totalAvailable = meta.results.total (P1); a no-match
3194
+ // query (openFDA HTTP 404 NOT_FOUND) ⇒ an honest empty (P2). An OPTIONAL OPENFDA_API_KEY
3195
+ // only raises the rate limit (keyless works ~1000/day).
3196
+ const OpenfdaDeviceClearancesInput = z.object({
3197
+ applicant: z
3198
+ .string()
3199
+ .min(1)
3200
+ .optional()
3201
+ .describe("Applicant / manufacturer name filter (→ applicant), e.g. 'medtronic'. Matched as an escaped Lucene phrase."),
3202
+ deviceName: z
3203
+ .string()
3204
+ .min(1)
3205
+ .optional()
3206
+ .describe("Device name filter (→ device_name), e.g. 'catheter'. Matched as an escaped Lucene phrase."),
3207
+ productCode: z
3208
+ .string()
3209
+ .min(1)
3210
+ .optional()
3211
+ .describe("FDA product code filter (→ product_code), e.g. 'DXN'. Matched as an escaped Lucene phrase."),
3212
+ clearanceType: z
3213
+ .string()
3214
+ .min(1)
3215
+ .optional()
3216
+ .describe("510(k) clearance type filter (→ clearance_type), e.g. 'Traditional', 'Special', 'Abbreviated'. Matched as an escaped Lucene phrase."),
3217
+ kNumber: z
3218
+ .string()
3219
+ .min(1)
3220
+ .optional()
3221
+ .describe("510(k) clearance number (K-number) filter (→ k_number), e.g. 'K123456'. Matched as an escaped Lucene phrase."),
3222
+ state: z
3223
+ .string()
3224
+ .regex(/^[A-Za-z]{2}$/)
3225
+ .optional()
3226
+ .describe("2-letter US state/territory postal code filter (→ state), e.g. 'CA'. Validated ^[A-Za-z]{2}$."),
3227
+ limit: z
3228
+ .number()
3229
+ .int()
3230
+ .min(1)
3231
+ .max(100)
3232
+ .optional()
3233
+ .describe("Max clearance records to return (default 25, max 100). Offset-paginated via skip."),
3234
+ skip: z
3235
+ .number()
3236
+ .int()
3237
+ .min(0)
3238
+ .optional()
3239
+ .describe("Row offset for pagination (default 0). Page with _meta.pagination.nextOffset."),
3240
+ });
3241
+ // ─── NHTSA vehicle safety (api.nhtsa.gov) — KEYLESS vehicle/parts supplier vetting ──
3242
+ // ADR-0057. Two tools (recalls + complaints) share make/model/modelYear inputs. NO
3243
+ // API key at all. ★The complaints VIN (PII) is excluded from the output. modelYear is
3244
+ // ^\d{4}$; make/model are letters/digits/space/hyphen only (SSRF/injection guard).
3245
+ const NhtsaVehicleInput = z.object({
3246
+ make: z
3247
+ .string()
3248
+ .regex(/^[A-Za-z0-9 -]+$/)
3249
+ .describe("Vehicle make (required), e.g. 'honda', 'ford'. Letters/digits/space/hyphen only (^[A-Za-z0-9 -]+$)."),
3250
+ model: z
3251
+ .string()
3252
+ .regex(/^[A-Za-z0-9 -]+$/)
3253
+ .describe("Vehicle model (required), e.g. 'accord', 'f-150'. Letters/digits/space/hyphen only (^[A-Za-z0-9 -]+$)."),
3254
+ modelYear: z
3255
+ .string()
3256
+ .regex(/^\d{4}$/)
3257
+ .describe("4-digit model year (required), e.g. '2020'. Validated ^\\d{4}$."),
3258
+ });
3259
+ // ─── CPSC consumer-product recalls (www.saferproducts.gov) — KEYLESS goods/import vetting ──
3260
+ // ADR-0058. One tool. NO API key at all. The response is a bare JSON ARRAY with no
3261
+ // total-count field / no pagination (totalAvailable = the returned count). All filters
3262
+ // optional; with NO filter the tool defaults RecallDateStart to ~90 days ago (disclosed)
3263
+ // rather than fetch the whole dataset. dates are ^\d{4}-\d{2}-\d{2}$; recallNumber is
3264
+ // letters/digits/hyphen only (SSRF/injection guard).
3265
+ const CpscRecallsInput = z.object({
3266
+ dateStart: z
3267
+ .string()
3268
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
3269
+ .optional()
3270
+ .describe("Recall date range START (optional), YYYY-MM-DD, e.g. '2025-01-01' (→ RecallDateStart). Validated ^\\d{4}-\\d{2}-\\d{2}$."),
3271
+ dateEnd: z
3272
+ .string()
3273
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
3274
+ .optional()
3275
+ .describe("Recall date range END (optional), YYYY-MM-DD, e.g. '2025-01-31' (→ RecallDateEnd). Validated ^\\d{4}-\\d{2}-\\d{2}$."),
3276
+ productName: z
3277
+ .string()
3278
+ .optional()
3279
+ .describe("Product name substring filter (optional), e.g. 'helmet' (→ ProductName)."),
3280
+ manufacturer: z
3281
+ .string()
3282
+ .optional()
3283
+ .describe("Manufacturer name substring filter (optional) (→ Manufacturer)."),
3284
+ recallNumber: z
3285
+ .string()
3286
+ .regex(/^[A-Za-z0-9-]+$/)
3287
+ .optional()
3288
+ .describe("A specific CPSC recall number (optional), e.g. '25088' (→ RecallNumber). Letters/digits/hyphen only (^[A-Za-z0-9-]+$)."),
3289
+ });
2883
3290
  // ─── BEA Regional Economic Accounts (apps.bea.gov) — the THIRD key-required source ──
2884
3291
  // ADR-0051. County/state/MSA GDP-by-industry (CAGDP2/SAGDP2N) + personal income
2885
3292
  // (CAINC1/SAINC1) — the regional/sub-national place-of-performance lane. REQUIRES a
@@ -3066,6 +3473,92 @@ const LdaSearchFilingsInput = z.object({
3066
3473
  .default(25)
3067
3474
  .describe("Filings per page, 1..25 (the LDA API caps at 25), default 25."),
3068
3475
  });
3476
+ // ─── US federal court opinions (www.courtlistener.com) — the litigation lane ──
3477
+ // ADR-0055. Federal court decisions (opinions) — the judicial signal no contract/
3478
+ // spending/lobbying source carries (e.g. uscfc bid-protest / contract-claim opinions).
3479
+ // ★PROVENANCE: CourtListener (Free Law Project, a non-profit), NOT a .gov API —
3480
+ // PACER (the .gov source) is paywalled. KEYLESS (anonymous 200); an optional free
3481
+ // COURTLISTENER_API_TOKEN only raises the rate limit, riding the Authorization: Token
3482
+ // … header ONLY (the lda/socrata app-token lineage). `count` is the REAL total —
3483
+ // never results.length; CURSOR pagination (nextCursor extracted from `next`). court/
3484
+ // dates charclass-guarded; all filter VALUES ride URLSearchParams; type=o is FIXED.
3485
+ const CourtlistenerSearchOpinionsInput = z.object({
3486
+ query: z
3487
+ .string()
3488
+ .min(1)
3489
+ .optional()
3490
+ .describe("Full-text query (maps to q), e.g. 'bid protest' or a party name. Matches across the opinion text/metadata."),
3491
+ court: z
3492
+ .string()
3493
+ .regex(/^[a-z0-9]+$/)
3494
+ .optional()
3495
+ .describe("A CourtListener court id (lowercase alphanumerics ^[a-z0-9]+$), e.g. 'uscfc' (US Court of Federal Claims — contract claims/bid protests), 'cafc' (Federal Circuit — contract/patent appeals), 'scotus'."),
3496
+ dateFiledAfter: z
3497
+ .string()
3498
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
3499
+ .optional()
3500
+ .describe("Only opinions filed on/after this ISO date (→ filed_after), e.g. '2020-01-01'. Validated ^\\d{4}-\\d{2}-\\d{2}$."),
3501
+ dateFiledBefore: z
3502
+ .string()
3503
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
3504
+ .optional()
3505
+ .describe("Only opinions filed on/before this ISO date (→ filed_before), e.g. '2024-12-31'. Validated ^\\d{4}-\\d{2}-\\d{2}$."),
3506
+ natureOfSuit: z
3507
+ .string()
3508
+ .min(1)
3509
+ .optional()
3510
+ .describe("Nature-of-suit text — folded into the q full-text query (the v4 opinions search has no verified dedicated filter), so it matches the text anywhere in the document (disclosed in _meta.notes)."),
3511
+ cursor: z
3512
+ .string()
3513
+ .min(1)
3514
+ .optional()
3515
+ .describe("Opaque continuation token for the NEXT page — pass back the _meta.nextCursor from the previous response (CourtListener uses CURSOR pagination, not page/offset)."),
3516
+ order: z
3517
+ .string()
3518
+ .min(1)
3519
+ .default("dateFiled desc")
3520
+ .describe("Sort order (maps to order_by), default 'dateFiled desc' (most recent first). E.g. 'dateFiled asc', 'score desc'."),
3521
+ });
3522
+ // ─── US tax-exempt nonprofits (projects.propublica.org) — the nonprofit lane ──
3523
+ // ADR-0060. IRS Form 990 public records republished KEYLESS by ProPublica Nonprofit
3524
+ // Explorer (a non-profit newsroom) — NOT a .gov API (the IRS has no clean query
3525
+ // API). ★PROVENANCE disclosed in _meta.source + a note. KEYLESS (no key of any
3526
+ // kind). search: q/state[id]/ntee[id]/page (0-based); total_results is the REAL
3527
+ // total — never organizations.length. All VALUES ride URLSearchParams (incl. the
3528
+ // bracket keys); state/ntee charclass/range-guarded.
3529
+ const NonprofitSearchInput = z.object({
3530
+ query: z
3531
+ .string()
3532
+ .min(1)
3533
+ .optional()
3534
+ .describe("Full-text query (maps to q) — an organization name or keyword, e.g. 'american red cross'. Matches across the org name/metadata."),
3535
+ state: z
3536
+ .string()
3537
+ .regex(/^[A-Za-z]{2}$/)
3538
+ .optional()
3539
+ .describe("Filter by a 2-letter US state/territory code (maps to state[id]), e.g. 'VA'. Validated ^[A-Za-z]{2}$."),
3540
+ ntee: z
3541
+ .number()
3542
+ .int()
3543
+ .min(1)
3544
+ .max(10)
3545
+ .optional()
3546
+ .describe("Filter by NTEE major category, an integer 1..10 (maps to ntee[id]) — the National Taxonomy of Exempt Entities top-level group (e.g. 1 Arts, 3 Environment, 8 Health)."),
3547
+ page: z
3548
+ .number()
3549
+ .int()
3550
+ .min(0)
3551
+ .default(0)
3552
+ .describe("0-BASED page number (default 0). Page with cur_page+1 from _meta.notes / when _meta.pagination.hasMore."),
3553
+ });
3554
+ // nonprofit_financials — one org's Form 990 profile + financials by EIN. KEYLESS.
3555
+ // ein rides the URL PATH ⇒ digits-only ^\d{1,9}$. An unknown EIN (404) ⇒ not_found.
3556
+ const NonprofitFinancialsInput = z.object({
3557
+ ein: z
3558
+ .string()
3559
+ .regex(/^\d{1,9}$/)
3560
+ .describe("The organization's EIN (Employer Identification Number), 1..9 digits, e.g. '530196605' (American National Red Cross). Validated ^\\d{1,9}$; rides the URL path."),
3561
+ });
3069
3562
  // api_key_status takes no input — it is a pure status query over process.env.
3070
3563
  const ApiKeyStatusInput = z.object({});
3071
3564
  // Build a ToolDef whose `handler` is type-checked against the schema's inferred
@@ -4331,16 +4824,90 @@ export const TOOLS = [
4331
4824
  // ━━━ US Census County Business Patterns — market sizing (1) ━━━ ADR-0047
4332
4825
  // ★The server's FIRST KEY-REQUIRED source: the Census Data API removed its
4333
4826
  // keyless tier, so WITHOUT a CENSUS_API_KEY this tool throws an honest
4334
- // invalid_input config error (the other 111 tools stay keyless). NAICS×geography
4827
+ // invalid_input config error (most other tools are keyless — see api_key_status). NAICS×geography
4335
4828
  // establishments / employment / annual payroll — the demand-side market-sizing
4336
4829
  // lane. Census negative suppression sentinels (-999999999 …) map to null (never
4337
4830
  // a negative number / never 0). The 2D-array body is parsed by header name.
4338
4831
  defineTool({
4339
4832
  name: "census_business_patterns",
4340
- description: "Market sizing by NAICS × geography — establishments, employment, and annual payroll from the US Census County Business Patterns (CBP) API (api.census.gov/data/{year}/cbp). ★REQUIRES a free CENSUS_API_KEY: the Census Data API has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://api.census.gov/data/key_signup.html; Census and FRED are the only key-required sources — every other tool is keyless). Input: optional `naics` (2–6 digit NAICS-2017, e.g. '5415'; omit to aggregate all sectors), `geography` (us|state|county, default us; county REQUIRES `state`), `state` (2-digit FIPS, e.g. '06'), `year` (default '2022'), optional `limit` (client-side top-N; CBP has no server pagination). Returns { rows:[{ name, geoId, naicsCode, naicsLabel, establishments, employees, annualPayrollUsd, state }] } + honest _meta. HONESTY: establishments/employees are integer counts and annualPayrollUsd is annual US dollars (×1000 from the source's $1,000-unit PAYANN); Census SUPPRESSED/withheld cells (large negative sentinels like -999999999) map to null — NEVER a negative number and NEVER 0 (a genuine 0 stays 0); geoId/naicsCode/state are STRINGS (leading zeros survive). CBP returns the COMPLETE geography set for the filter (no pagination) ⇒ totalAvailable = the row count, complete:true. A missing/invalid key ⇒ invalid_input (a 302 to the Missing-Key page); a header-only body ⇒ honest empty (returned:0); a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The key rides ONLY in the &key= query param — never logged or echoed.",
4833
+ description: "Market sizing by NAICS × geography — establishments, employment, and annual payroll from the US Census County Business Patterns (CBP) API (api.census.gov/data/{year}/cbp). ★REQUIRES a free CENSUS_API_KEY: the Census Data API has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://api.census.gov/data/key_signup.html; call api_key_status to see every source's key requirement). Input: optional `naics` (2–6 digit NAICS-2017, e.g. '5415'; omit to aggregate all sectors), `geography` (us|state|county, default us; county REQUIRES `state`), `state` (2-digit FIPS, e.g. '06'), `year` (default '2022'), optional `limit` (client-side top-N; CBP has no server pagination). Returns { rows:[{ name, geoId, naicsCode, naicsLabel, establishments, employees, annualPayrollUsd, state }] } + honest _meta. HONESTY: establishments/employees are integer counts and annualPayrollUsd is annual US dollars (×1000 from the source's $1,000-unit PAYANN); large-negative suppression sentinels map to null — NEVER a negative number and NEVER 0 (a genuine 0 stays 0; note CBP primarily uses noise-infusion + suppression flags, surfaced as reported — see the tool's suppression note); geoId/naicsCode/state are STRINGS (leading zeros survive). CBP returns the COMPLETE geography set for the filter (no pagination) ⇒ totalAvailable = the row count, complete:true. A missing/invalid key ⇒ invalid_input (a 302 to the Missing-Key page); a header-only body ⇒ honest empty (returned:0); a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The key rides ONLY in the &key= query param — never logged or echoed.",
4341
4834
  inputSchema: CensusBusinessPatternsInput,
4342
4835
  handler: (input) => censusEconomic.businessPatterns(input),
4343
4836
  }),
4837
+ // ━━━ EPA Envirofacts TRI facilities — environmental footprint (1) ━━━ ADR-0059
4838
+ // KEYLESS (data.epa.gov /efservice/tri_facility). ★Two requests: a count
4839
+ // sub-query yields the EXACT total (P1 — TOTALQUERYRESULTS, e.g. VA=1247), then
4840
+ // the data slice. All user values ride as PATH SEGMENTS → each is
4841
+ // charclass-validated + encodeURIComponent-encoded (the load-bearing SSRF guard).
4842
+ defineTool({
4843
+ name: "epa_tri_facilities",
4844
+ description: "Look up EPA Toxics Release Inventory (TRI) reporting facilities by state / facility-name / county — an environmental-footprint / place-of-performance screen (EPA Envirofacts, keyless; data.epa.gov/efservice/tri_facility). Input: `state` (2-letter, e.g. 'VA'), `facilityName` (partial match, e.g. 'chemical'), `county` (partial match) — provide at least `state` OR `facilityName` (an all-empty query is refused); optional `limit` (1–100, default 25), `offset`. Returns { facilities:[{ triFacilityId, facilityName, streetAddress, city, county, state, zip, region, closed }] } + honest _meta. ★HONESTY: totalAvailable is the EXACT count from a SEPARATE count sub-query (…/count/JSON → TOTALQUERYRESULTS), NEVER the returned-rows length; if that count fails, totalAvailable is null + a disclosing note (never length-faked). offset/limit pagination (hasMore = offset+returned < total). `closed` normalizes fac_closed_ind ('0'/'N'→false, '1'/'Y'→true, unrecognized→null — never a fabricated false); addresses/names are null-never-empty-string. A genuine no-match ⇒ honest empty (returned:0); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array/non-JSON ⇒ schema_drift. These are nominal TRI reporters, NOT a compliance/enforcement determination. KEYLESS — no key is sent.",
4845
+ inputSchema: EpaTriFacilitiesInput,
4846
+ handler: (input) => epaEnvirofacts.triFacilities(input),
4847
+ }),
4848
+ // ━━━ CMS Medicare provider-service utilization — healthcare market (1) ━━━ ADR-0061
4849
+ // KEYLESS (data.cms.gov /data-api/v1/dataset/{uuid}). ★Two requests: a stats
4850
+ // count sub-query yields the EXACT per-filter total (P1 — found_rows, e.g.
4851
+ // VA=278254), then the data slice (a bare JSON array). All filter VALUES ride via
4852
+ // URLSearchParams (bracket key + value encoded — the SSRF guard). REQUIRE npi OR
4853
+ // state (the 9.78M-row table is never scanned unscoped). The dataset UUID is a
4854
+ // SPECIFIC ANNUAL VINTAGE (surfaced in a _meta note; update yearly).
4855
+ defineTool({
4856
+ name: "cms_medicare_provider_services",
4857
+ description: "Look up Medicare Part-B provider utilization — for a given provider (NPI) or state, the HCPCS services rendered, beneficiaries served, and submitted / Medicare-allowed / Medicare-paid amounts (CMS 'Medicare Physician & Other Practitioners — by Provider and Service', keyless; data.cms.gov data-API). The demand-side complement to nppes_lookup_provider (who providers ARE → what they BILL) for healthcare-market / competitor / teaming due-diligence. Input: `npi` (10-digit) OR `state` (2-letter) — at least ONE is REQUIRED (the table is 9.78M rows; an all-empty query is refused; providerType/hcpcsCode alone are NOT enough to scope); optional `providerType` (exact CMS specialty, e.g. 'Family Practice'), `hcpcsCode` (e.g. '97110', 'G0463'), `size` (1–100, default 25), `offset`. Returns { services:[{ npi, providerName, credentials, providerType, city, state, zip, hcpcsCode, hcpcsDescription, totalBeneficiaries, totalServices, avgSubmittedCharge, avgMedicareAllowed, avgMedicarePayment }] } + honest _meta. ★HONESTY: totalAvailable is the EXACT count from a SEPARATE stats sub-query (…/data-viewer/stats → found_rows, e.g. VA=278254), NEVER the returned-rows length; if that count fails, totalAvailable is null + a disclosing note (never length-faked). offset/size pagination (hasMore = offset+returned < total). Aggregate/payment values are numeric-string → number|null (a genuine 0 stays 0, absent → null, never 0-faked); NPI/HCPCS/names are null-never-empty-string. A genuine no-match ⇒ honest empty (returned:0); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array/non-JSON ⇒ schema_drift. These are public PROVIDER-level AGGREGATE figures (no patient identifiers) for ONE annual vintage (the dataset year is disclosed in _meta) — a utilization snapshot, NOT a fraud/quality/fitness determination. KEYLESS — no key is sent.",
4858
+ inputSchema: CmsMedicareProviderServicesInput,
4859
+ handler: (input) => cmsUtilization.providerServices(input),
4860
+ }),
4861
+ // ━━━ CMS Hospital Compare — Hospital General Information (1) ━━━ ADR-0062
4862
+ // KEYLESS (data.cms.gov /provider-data/api/1/datastore/query/{datasetId}). A
4863
+ // SINGLE request: the response's top-level `count` is the EXACT per-filter total
4864
+ // (P1 — VA=96), never the slice length. Filters ride as DKAN conditions[] triples
4865
+ // via URLSearchParams (bracket key + value encoded — the SSRF guard): state is an
4866
+ // EXACT match, facilityName/hospitalType are case-insensitive substring matches,
4867
+ // AND-combined server-side. REQUIRE state OR facilityName (never scanned unscoped).
4868
+ defineTool({
4869
+ name: "cms_hospital_compare",
4870
+ description: "Look up Medicare-certified hospitals by US state and/or facility-name fragment — location, type, ownership, emergency-services flag, and CMS star rating (CMS Hospital Compare 'Hospital General Information', keyless; data.cms.gov provider-data datastore-query API, ~5,432 hospitals). A healthcare-facility directory / market-map lane (WHERE hospitals are and HOW CMS rates them). Input: `state` (2-letter, EXACT) OR `facilityName` (a name fragment, case-insensitive substring/contains match) — at least ONE is REQUIRED (an all-empty query is refused; hospitalType alone is NOT enough to scope); optional `hospitalType` (substring, e.g. 'Acute', 'Critical Access'), `size` (1–100, default 25), `offset`. Returns { hospitals:[{ facilityId, facilityName, address, city, state, zip, county, phone, hospitalType, ownership, emergencyServices, overallRating }] } + honest _meta. ★HONESTY: totalAvailable is the response's EXACT top-level `count` for the filter set (VA=96), NEVER the returned-rows length; offset/size pagination (hasMore = offset+returned < count). overallRating is CMS's 1–5 star rating as a number; 'Not Available'/blank/non-numeric ⇒ null (NEVER 0). emergencyServices normalizes 'Yes'⇒true / 'No'⇒false / else null (never a fabricated false). IDs/names/addresses are null-never-empty-string. A genuine no-match ⇒ honest empty (returned:0); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array body or one missing count/results ⇒ schema_drift. Filters are applied SERVER-SIDE (AND-combined) — nothing is silently dropped. This is a summary star rating, NOT a clinical-quality or fitness determination. KEYLESS — no key is sent.",
4871
+ inputSchema: CmsHospitalCompareInput,
4872
+ handler: (input) => cmsHospital.hospitalCompare(input),
4873
+ }),
4874
+ // ━━━ CMS Facility Directory — 4 provider-data datasets (1) ━━━ ADR-0063
4875
+ // KEYLESS (data.cms.gov /provider-data/api/1/datastore/query/{datasetId}). A
4876
+ // generalization of cms_hospital_compare beyond hospitals: facilityType (a Zod
4877
+ // enum) indexes a CONSTANT map to a VETTED dataset id — the user value never enters
4878
+ // the path (the load-bearing SSRF guard). A SINGLE request: the response's top-level
4879
+ // `count` is the EXACT per-filter total (P1). name/address/ownership columns vary
4880
+ // per dataset → coalesced (null if none — never empty-string, never fabricated).
4881
+ defineTool({
4882
+ name: "cms_facility_directory",
4883
+ description: "Look up Medicare/Medicaid-certified healthcare FACILITIES by type — nursing homes, home health agencies, hospices, or dialysis facilities — with their name, address, city, state, zip, and ownership (CMS provider-data, keyless; data.cms.gov datastore-query API, four datasets). A healthcare-facility directory / market-map lane that generalizes cms_hospital_compare beyond hospitals. Input: `facilityType` (REQUIRED enum — 'nursing_home' ~14,695 / 'home_health' ~12,460 / 'hospice' ~6,852 / 'dialysis' ~7,490; selects the dataset id via a constant map, the value never enters the URL path), optional `state` (2-letter, EXACT), `facilityName` (a name fragment, case-insensitive substring/contains match against the dataset's primary-name column), `size` (1–100, default 25), `offset`. Returns { facilities:[{ name, address, city, state, zip, facilityType, ownership }] } + honest _meta. ★HONESTY: totalAvailable is the response's EXACT top-level `count` for the filter set, NEVER the returned-rows length; offset/size pagination (hasMore = offset+returned < count). name/address/ownership column names DIFFER across the four datasets, so each is COALESCED over per-dataset candidates (name: provider_name/facility_name/legal_business_name; address: address/provider_address/address_line_1; ownership: ownership_type/type_of_ownership/profit_or_nonprofit) — a field absent in the chosen dataset is null (unknown), NEVER an empty string and NEVER fabricated. facilityType is echoed on each row. A genuine no-match ⇒ honest empty (returned:0); an invalid facilityType ⇒ invalid_input (blocked by the enum); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array body or one missing count/results ⇒ schema_drift. Filters are applied SERVER-SIDE (AND-combined) — nothing is silently dropped. This is a facility directory, NOT a clinical-quality or fitness determination. KEYLESS — no key is sent.",
4884
+ inputSchema: CmsFacilityDirectoryInput,
4885
+ handler: (input) => cmsFacility.facilityDirectory(input),
4886
+ }),
4887
+ // ━━━ CMS DMEPOS by Supplier — supply-side utilization (1) ━━━ ADR-0064
4888
+ // KEYLESS (data.cms.gov /data-api/v1/dataset/{uuid}). ★Two requests: a stats count
4889
+ // sub-query yields the EXACT per-filter total (P1 — found_rows), then the data slice
4890
+ // (a bare JSON array). All filter VALUES ride via URLSearchParams (bracket key +
4891
+ // value encoded — the SSRF guard). REQUIRE npi OR state (the supplier table is never
4892
+ // scanned unscoped). The dataset UUID is a SPECIFIC ANNUAL VINTAGE (update yearly).
4893
+ defineTool({
4894
+ name: "cms_dmepos_suppliers",
4895
+ description: "Look up Medicare DMEPOS (Durable Medical Equipment, Devices & Supplies) SUPPLIERS — for a given supplier (NPI) or state, the supplier's identity plus aggregate Medicare figures: HCPCS codes billed, beneficiaries served, claims, services, and submitted / Medicare-allowed / Medicare-paid amounts (CMS 'Medicare DMEPOS — by Supplier', keyless; data.cms.gov data-API). The supply-side complement to cms_medicare_provider_services for healthcare-market / competitor / teaming due-diligence on equipment suppliers. Input: `npi` (10-digit) OR `state` (2-letter) — at least ONE is REQUIRED (an all-empty query is refused; the supplier table is never scanned unscoped); optional `size` (1–100, default 25), `offset`. Returns { suppliers:[{ npi, supplierName, credentials, entityType, city, state, zip, totalHcpcsCodes, totalBeneficiaries, totalClaims, totalServices, submittedCharges, medicareAllowed, medicarePayment }] } + honest _meta. ★HONESTY: totalAvailable is the EXACT count from a SEPARATE stats sub-query (…/data-viewer/stats → found_rows), NEVER the returned-rows length; if that count fails, totalAvailable is null + a disclosing note (never length-faked). offset/size pagination (hasMore = offset+returned < total). Aggregate/payment values are numeric-string → number|null (a genuine 0 stays 0, absent → null, never 0-faked); NPI/entityType/names are null-never-empty-string; supplierName joins Last_Name_Org + First_Name ('Last, First' for individuals, the org name alone for organizations). A genuine no-match ⇒ honest empty (returned:0); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array/non-JSON ⇒ schema_drift. These are public SUPPLIER-level AGGREGATE figures (no patient identifiers) for ONE annual vintage (disclosed in _meta) — a utilization snapshot, NOT a fraud/quality/fitness determination. KEYLESS — no key is sent.",
4896
+ inputSchema: CmsDmeposSuppliersInput,
4897
+ handler: (input) => cmsSupplier.dmeposSuppliers(input),
4898
+ }),
4899
+ // ━━━ CMS Revoked Medicare Providers & Suppliers — vetting/exclusion list (1) ━━━ ADR-0064
4900
+ // KEYLESS (data.cms.gov /data-api/v1/dataset/{uuid}). A legally-published
4901
+ // revocation/exclusion register (~7,059 rows) — the SAME vetting class as the OFAC /
4902
+ // SAM exclusion lists already shipped (surfacing the names IS the point). ALL filters
4903
+ // optional (small table — pagination is fine unfiltered). SAME two-request stats-count
4904
+ // P1 pattern; filter VALUES ride via URLSearchParams (bracket key + value encoded).
4905
+ defineTool({
4906
+ name: "cms_revoked_providers",
4907
+ description: "Search CMS's PUBLIC 'Revoked Medicare Providers & Suppliers' list — the legally-published register of Medicare enrollment revocations, with the revoked provider/supplier's identity, provider type, revocation reason, effective date, and re-enrollment-bar expiration (CMS 'Revoked Providers and Suppliers', keyless; data.cms.gov data-API, ~7,059 rows). A vetting / due-diligence lane in the SAME class as the OFAC / SAM-exclusions lists — for screening a counterparty before teaming or subcontracting. Input (ALL optional — the ~7K-row list is safe to page unfiltered): `npi` (10-digit → NPI), `state` (2-letter → STATE_CD, exact), `lastName` (→ LAST_NAME, exact), `size` (1–100, default 25), `offset`. Returns { revocations:[{ enrollmentId, npi, name, state, providerType, revocationReason, revocationEffectiveDate, reenrollmentBarExpiration }] } + honest _meta (which notes this is CMS's public revocation/exclusion list — a due-diligence signal, NOT a current-eligibility, guilt, or fitness determination). ★HONESTY: totalAvailable is the EXACT count from a SEPARATE stats sub-query (…/data-viewer/stats → found_rows), NEVER the returned-rows length; if that count fails, totalAvailable is null + a disclosing note (never length-faked). offset/size pagination (hasMore = offset+returned < total). name coalesces ORG_NAME (organizations) else FIRST_NAME + LAST_NAME (individuals) — null if none, never a fabricated empty; NPI/reasons/dates are strings (null-never-empty-string). A genuine no-match ⇒ honest empty (returned:0); a 4xx ⇒ invalid_input/not_found; a 5xx ⇒ THROWS; a 200 non-array/non-JSON ⇒ schema_drift. KEYLESS — no key is sent.",
4908
+ inputSchema: CmsRevokedProvidersInput,
4909
+ handler: (input) => cmsSupplier.revokedProviders(input),
4910
+ }),
4344
4911
  // ━━━ FRED (Federal Reserve Economic Data) — macro context (2) ━━━ ADR-0048
4345
4912
  // ★The server's SECOND KEY-REQUIRED source: FRED has NO keyless tier, so WITHOUT
4346
4913
  // a FRED_API_KEY both tools throw an honest invalid_input config error (the other
@@ -4348,7 +4915,7 @@ export const TOOLS = [
4348
4915
  // bid escalation / market timing. A missing observation ('.') maps to null (never 0).
4349
4916
  defineTool({
4350
4917
  name: "fred_search_series",
4351
- description: "Discover FRED economic series (GDP, CPI, interest rates, unemployment, PPI…) by free-text search (FRED /fred/series/search; api.stlouisfed.org). ★REQUIRES a free FRED_API_KEY: FRED has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://fred.stlouisfed.org/docs/api/api_key.html; this and fred_series_observations are the key-required macro tools — the other 112 tools stay keyless). Input: `query` (the search_text, required, e.g. 'unemployment rate' / 'CPI' / '10-year treasury'), optional `limit` (default 25, max 1000), `offset`. Returns { series:[{ id, title, frequency, frequencyShort, units, seasonalAdjustment, observationStart, observationEnd, lastUpdated, popularity }] } + honest _meta. Feed `id` into fred_series_observations for the time series. HONESTY: totalAvailable is FRED's EXACT reported `count` (offset pagination via hasMore/nextOffset — never fabricated); every scalar is null-never-empty-string; a genuine no-match ⇒ honest empty (returned:0); a 400 (bad/missing key) ⇒ invalid_input CARRYING FRED's error_message; a 5xx ⇒ THROWS; a 200 non-JSON / non-array `seriess` ⇒ schema_drift. The key rides ONLY in the &api_key= query param — never logged or echoed.",
4918
+ description: "Discover FRED economic series (GDP, CPI, interest rates, unemployment, PPI…) by free-text search (FRED /fred/series/search; api.stlouisfed.org). ★REQUIRES a free FRED_API_KEY: FRED has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://fred.stlouisfed.org/docs/api/api_key.html; fred_series_observations shares this key — call api_key_status to see every source's key requirement). Input: `query` (the search_text, required, e.g. 'unemployment rate' / 'CPI' / '10-year treasury'), optional `limit` (default 25, max 1000), `offset`. Returns { series:[{ id, title, frequency, frequencyShort, units, seasonalAdjustment, observationStart, observationEnd, lastUpdated, popularity }] } + honest _meta. Feed `id` into fred_series_observations for the time series. HONESTY: totalAvailable is FRED's EXACT reported `count` (offset pagination via hasMore/nextOffset — never fabricated); every scalar is null-never-empty-string; a genuine no-match ⇒ honest empty (returned:0); a 400 (bad/missing key) ⇒ invalid_input CARRYING FRED's error_message; a 5xx ⇒ THROWS; a 200 non-JSON / non-array `seriess` ⇒ schema_drift. The key rides ONLY in the &api_key= query param — never logged or echoed.",
4352
4919
  inputSchema: FredSearchSeriesInput,
4353
4920
  handler: (input) => fred.searchSeries(input),
4354
4921
  }),
@@ -4358,17 +4925,75 @@ export const TOOLS = [
4358
4925
  inputSchema: FredSeriesObservationsInput,
4359
4926
  handler: (input) => fred.seriesObservations(input),
4360
4927
  }),
4928
+ // ━━━ openFDA recall/enforcement (api.fda.gov) — product-safety recalls (1) ━━━ ADR-0054
4929
+ // KEYLESS with an OPTIONAL OPENFDA_API_KEY (raises the rate limit; keyless works
4930
+ // ~1000/day — it NEVER throws for a missing key, unlike the key-REQUIRED sources).
4931
+ // Structured filters ONLY (no raw Lucene passthrough — the tool assembles + escapes
4932
+ // the search= string, injection-safe). ★P1: totalAvailable = meta.results.total
4933
+ // (EXACT). ★P2 crux: a no-match query returns openFDA HTTP 404 NOT_FOUND ⇒ an honest
4934
+ // empty, never a throw. The optional key rides &api_key= ONLY.
4935
+ defineTool({
4936
+ name: "openfda_enforcement",
4937
+ description: "Search openFDA recall/enforcement records — drug/device/food product recalls with the recalling firm, product, reason, FDA classification (Class I/II/III), status, and geography (openFDA /{category}/enforcement.json; api.fda.gov). KEYLESS (an OPTIONAL free OPENFDA_API_KEY only RAISES the rate limit — keyless works at ~1000 requests/day; it NEVER throws for a missing key; get one at https://open.fda.gov/apis/authentication/; call api_key_status to see every source's key requirement). Input: `category` (drug|device|food, default drug), and STRUCTURED filters — `firm` (→recalling_firm), `product` (→product_description), `reason` (→reason_for_recall), `classification` (Class I|II|III), `status` (e.g. Ongoing/Terminated/Completed), `state` (2-letter, e.g. 'CA') — the tool safely assembles + escapes these into the openFDA search= Lucene string (NO raw passthrough — injection-safe), plus `limit` (1..100, default 25) and `skip` (offset ≥0). Returns { recalls:[{ recallingFirm, productDescription, reasonForRecall, classification, status, state, city, recallInitiationDate, recallNumber, voluntaryMandated, distributionPattern }] } + honest _meta. HONESTY: totalAvailable is openFDA's EXACT meta.results.total (skip/limit pagination via hasMore/nextOffset — never results.length); every scalar (dates included, recall_initiation_date is a YYYYMMDD string) is null-never-empty-string. ★A no-match query returns openFDA HTTP 404 NOT_FOUND ⇒ an HONEST EMPTY (returned:0, totalAvailable:0), NOT an error; a 400 syntax error ⇒ invalid_input surfacing openFDA's message; a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The optional key rides ONLY the &api_key= query param — never logged or echoed.",
4938
+ inputSchema: OpenfdaEnforcementInput,
4939
+ handler: (input) => openfda.enforcement(input),
4940
+ }),
4941
+ // ━━━ openFDA 510(k) device clearances (api.fda.gov) — medical-device regulatory (1) ━━━ ADR-0056
4942
+ // KEYLESS with an OPTIONAL OPENFDA_API_KEY (raises the rate limit; keyless works
4943
+ // ~1000/day — never throws for a missing key). SAME source/envelope/crux as
4944
+ // openfda_enforcement (reuses openfda.ts's fetchOpenfda/readOpenfdaError/luceneQuote).
4945
+ // Structured filters ONLY (no raw Lucene passthrough — the tool assembles + escapes the
4946
+ // search= string, injection-safe). ★P1: totalAvailable = meta.results.total (EXACT,
4947
+ // ~175507). ★P2 crux: a no-match query returns openFDA HTTP 404 NOT_FOUND ⇒ an honest
4948
+ // empty, never a throw. The optional key rides &api_key= ONLY.
4949
+ defineTool({
4950
+ name: "openfda_device_clearances",
4951
+ description: "Search openFDA 510(k) DEVICE CLEARANCES — the FDA's premarket-notification (510(k)) clearances for medical devices, with the applicant/manufacturer, device name, clearance number (K-number), decision (date + description), clearance type, product code, advisory committee, and geography (openFDA /device/510k.json; api.fda.gov). KEYLESS (an OPTIONAL free OPENFDA_API_KEY only RAISES the rate limit — keyless works at ~1000 requests/day; it NEVER throws for a missing key; get one at https://open.fda.gov/apis/authentication/; call api_key_status to see every source's key requirement). Input: STRUCTURED filters — `applicant` (→applicant), `deviceName` (→device_name), `productCode` (→product_code), `clearanceType` (→clearance_type, e.g. Traditional/Special/Abbreviated), `kNumber` (→k_number, e.g. 'K123456'), `state` (2-letter, e.g. 'CA') — the tool safely assembles + escapes these into the openFDA search= Lucene string (NO raw passthrough — injection-safe), plus `limit` (1..100, default 25) and `skip` (offset ≥0). Returns { clearances:[{ applicant, deviceName, kNumber, decisionDate, decisionDescription, clearanceType, productCode, advisoryCommittee, state }] } + honest _meta. HONESTY: totalAvailable is openFDA's EXACT meta.results.total (skip/limit pagination via hasMore/nextOffset — never results.length); every scalar (dates included, decision_date is a YYYY-MM-DD string) is null-never-empty-string. ★A no-match query returns openFDA HTTP 404 NOT_FOUND ⇒ an HONEST EMPTY (returned:0, totalAvailable:0), NOT an error; a 400 syntax error ⇒ invalid_input surfacing openFDA's message; a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The optional key rides ONLY the &api_key= query param — never logged or echoed.",
4952
+ inputSchema: OpenfdaDeviceClearancesInput,
4953
+ handler: (input) => openfdaDevice.deviceClearances(input),
4954
+ }),
4955
+ // ━━━ NHTSA vehicle safety (api.nhtsa.gov) — vehicle/parts supplier vetting (2) ━━━ ADR-0057
4956
+ // ★KEYLESS — no API key at all (no parameter, no header). The cross-agency
4957
+ // product-safety family alongside openFDA (medical). Both tools share
4958
+ // make/model/modelYear inputs and return the COMPLETE matching set (no pagination
4959
+ // ⇒ totalAvailable = the upstream Count/count, complete:true). ★The complaints VIN
4960
+ // (an individual-vehicle PII identifier) is EXCLUDED from the output.
4961
+ defineTool({
4962
+ name: "nhtsa_recalls",
4963
+ description: "Look up NHTSA vehicle safety RECALLS for a specific vehicle — the manufacturer's recall campaigns with the affected component, the safety consequence, the remedy, and 'do not drive'/'park outside'/over-the-air-update flags (NHTSA /recalls/recallsByVehicle; api.nhtsa.gov). KEYLESS — no API key is required or accepted. Input: `make` (required, e.g. 'honda'), `model` (required, e.g. 'accord'), `modelYear` (required, 4-digit, e.g. '2020'). Returns { recalls:[{ campaignNumber, manufacturer, component, summary, consequence, remedy, reportReceivedDate, parkIt, parkOutside, overTheAirUpdate }] } + honest _meta. HONESTY: totalAvailable is NHTSA's EXACT Count and NHTSA returns the COMPLETE set for the vehicle (no pagination) ⇒ complete:true; a no-match (Count 0 / a bad make/model) ⇒ an HONEST EMPTY (returned:0), NOT an error; a 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The park-it/park-outside/over-the-air-update flags are preserved as booleans (never a fabricated false); dates are strings; every scalar is null-never-empty-string. Fixed host api.nhtsa.gov (SSRF-guarded); make/model are letters/digits/space/hyphen only and modelYear is ^\\d{4}$.",
4964
+ inputSchema: NhtsaVehicleInput,
4965
+ handler: (input) => nhtsa.recalls(input),
4966
+ }),
4967
+ defineTool({
4968
+ name: "nhtsa_complaints",
4969
+ description: "Look up NHTSA consumer COMPLAINTS for a specific vehicle — owner-filed safety complaints with the affected component, crash/fire flags, injury/death counts, and incident/filing dates (NHTSA /complaints/complaintsByVehicle; api.nhtsa.gov). KEYLESS — no API key is required or accepted. Input: `make` (required, e.g. 'honda'), `model` (required, e.g. 'accord'), `modelYear` (required, 4-digit, e.g. '2020'). Returns { complaints:[{ odiNumber, manufacturer, component, summary, crash, fire, numberOfInjuries, numberOfDeaths, dateOfIncident, dateComplaintFiled }] } + honest _meta. ★PRIVACY: the NHTSA complaint VIN (an individual-vehicle identifier) is INTENTIONALLY EXCLUDED from the output — the B2G signal is the manufacturer/component/crash/fire/injury/death safety history, not the VIN. HONESTY: totalAvailable is NHTSA's EXACT count and NHTSA returns the COMPLETE set for the vehicle (no pagination) ⇒ complete:true; a no-match ⇒ an HONEST EMPTY (returned:0), NOT an error; crash/fire preserved as booleans (never a fabricated false); numberOfInjuries/numberOfDeaths via numeric coercion (a genuine 0 stays 0, NEVER null-for-0); dates are strings; a 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. Fixed host api.nhtsa.gov (SSRF-guarded); make/model are letters/digits/space/hyphen only and modelYear is ^\\d{4}$.",
4970
+ inputSchema: NhtsaVehicleInput,
4971
+ handler: (input) => nhtsa.complaints(input),
4972
+ }),
4973
+ // ━━━ CPSC consumer-product recalls (www.saferproducts.gov) — goods/import vetting (1) ━━━ ADR-0058
4974
+ // ★KEYLESS — no API key at all (no parameter, no header). The third leg of the
4975
+ // cross-agency product-safety family alongside NHTSA (vehicles) and openFDA
4976
+ // (medical). The response is a bare JSON ARRAY with NO total-count field and NO
4977
+ // pagination ⇒ totalAvailable = the returned count, complete:true. All filters are
4978
+ // optional; with NO filter the tool bounds results to a ~90-day default window
4979
+ // (disclosed) rather than silently fetch the entire dataset.
4980
+ defineTool({
4981
+ name: "cpsc_recalls",
4982
+ description: "Look up U.S. CPSC consumer-product RECALLS — the recall title, hazard description, remedy, affected products, manufacturers, retailers, injuries, and country of manufacture (CPSC SaferProducts /RestWebServices/Recall; www.saferproducts.gov). The consumer-goods / import product-safety lane alongside nhtsa_recalls (vehicles) and openfda (medical). KEYLESS — no API key is required or accepted. Inputs (ALL optional): `dateStart`/`dateEnd` (YYYY-MM-DD recall date range), `productName` (substring), `manufacturer` (substring), `recallNumber` (a specific CPSC recall number). Returns { recalls:[{ recallNumber, recallDate, title, description, url, products:[names], numberOfUnits, manufacturers:[names], retailers:[names], hazards:[descriptions], remedies:[descriptions], injuries:[names], manufacturerCountries:[names] }] } + honest _meta. HONESTY: the CPSC response is a bare array with NO count field and NO pagination — it returns the COMPLETE matching set, so totalAvailable = the number of returned recalls and complete:true (never a fabricated total). ★With NO filter given, results are bounded to a DEFAULT ~90-day recent window (RecallDateStart, disclosed in _meta.notes) rather than a silent whole-dataset fetch. An empty result ⇒ an HONEST EMPTY (returned:0), NOT an error; a 4xx ⇒ invalid_input; a 5xx/timeout ⇒ THROWS; a 200 non-JSON OR a non-array body ⇒ schema_drift. Nested arrays are flattened to name/description strings (an empty {} object is skipped, never fabricated); NumberOfUnits is free text kept as a string; dates are strings; every scalar is null-never-empty-string. Fixed host www.saferproducts.gov (SSRF-guarded); dates are ^\\d{4}-\\d{2}-\\d{2}$ and recallNumber is letters/digits/hyphen only.",
4983
+ inputSchema: CpscRecallsInput,
4984
+ handler: (input) => cpsc.recalls(input),
4985
+ }),
4361
4986
  // ━━━ BEA Regional Economic Accounts (apps.bea.gov) — regional GDP/income (1) ━━━ ADR-0051
4362
4987
  // ★The server's THIRD KEY-REQUIRED source: the BEA Data API has NO keyless tier, so
4363
4988
  // WITHOUT a BEA_API_KEY this tool throws an honest invalid_input config error (the
4364
- // other 116 tools stay keyless). County/state/MSA GDP-by-industry + personal income —
4989
+ // most other tools stay keyless — see api_key_status). County/state/MSA GDP-by-industry + personal income —
4365
4990
  // the regional place-of-performance lane. ★The P2 crux: a missing/invalid key returns
4366
4991
  // HTTP 200 carrying BEAAPI.Results.Error (NOT an HTTP error status), which is detected
4367
4992
  // BEFORE the Data-array drift check and surfaced as invalid_input (never a fake empty).
4368
4993
  // DataValue is a comma-formatted string; suppression codes ((NA)/(D)/(NM)/(L)/*) → null.
4369
4994
  defineTool({
4370
4995
  name: "bea_regional_data",
4371
- description: "Regional (county / state / MSA) economic data — GDP by industry and personal income — from the US Bureau of Economic Analysis (BEA) Regional Economic Accounts (apps.bea.gov/api/data, dataset 'Regional'). ★REQUIRES a free BEA_API_KEY: the BEA Data API has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://apps.bea.gov/API/signup/; Census, FRED, and BEA are the only key-required sources — every other tool is keyless). Input: `tableName` (required, e.g. 'CAGDP2' county GDP by industry, 'SAGDP2N' state GDP, 'CAINC1'/'SAINC1' personal income), `geoFips` (required — 'STATE' for all states, a county FIPS like '06075', or an MSA code), `lineCode` (required — an integer industry line like '1', or 'ALL'), optional `year` ('LAST5' default, a 4-digit year, or 'ALL'), `frequency` ('A' annual default, or 'Q'). Returns { rows:[{ geoFips, geoName, timePeriod, lineCode, dataValue, unitOfMeasure, unitMult, noteRef }], notes:[{ noteRef, noteText }] } + honest _meta. ★HONESTY (the crux): a missing/invalid key — or ANY bad parameter — returns HTTP 200 carrying an Error object (NOT an HTTP error status); this is detected and surfaced as invalid_input carrying BEA's APIErrorDescription — NEVER a fake empty. dataValue is parsed from BEA's comma-formatted string ('1,234,567'→1234567); BEA suppression/not-available codes ((NA)/(D)/(NM)/(L)/*) map to null — NEVER 0 (a genuine 0 stays 0). unitMult (a power-of-10 multiplier) and unitOfMeasure are reported ALONGSIDE the raw dataValue — the value is NOT multiplied in (apply unitMult yourself). BEA returns the COMPLETE set for the filter (no pagination) ⇒ totalAvailable = the row count, complete:true; a genuine empty Data:[] ⇒ honest empty (returned:0); a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The key rides ONLY in the UserID= query param — never logged or echoed.",
4996
+ description: "Regional (county / state / MSA) economic data — GDP by industry and personal income — from the US Bureau of Economic Analysis (BEA) Regional Economic Accounts (apps.bea.gov/api/data, dataset 'Regional'). ★REQUIRES a free BEA_API_KEY: the BEA Data API has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://apps.bea.gov/API/signup/; call api_key_status to see every source's key requirement). Input: `tableName` (required, e.g. 'CAGDP2' county GDP by industry, 'SAGDP2N' state GDP, 'CAINC1'/'SAINC1' personal income), `geoFips` (required — 'STATE' for all states, a county FIPS like '06075', or an MSA code), `lineCode` (required — an integer industry line like '1', or 'ALL'), optional `year` ('LAST5' default, a 4-digit year, or 'ALL'), `frequency` ('A' annual default, or 'Q'). Returns { rows:[{ geoFips, geoName, timePeriod, lineCode, dataValue, unitOfMeasure, unitMult, noteRef }], notes:[{ noteRef, noteText }] } + honest _meta. ★HONESTY (the crux): a missing/invalid key — or ANY bad parameter — returns HTTP 200 carrying an Error object (NOT an HTTP error status); this is detected and surfaced as invalid_input carrying BEA's APIErrorDescription — NEVER a fake empty. dataValue is parsed from BEA's comma-formatted string ('1,234,567'→1234567); BEA suppression/not-available codes ((NA)/(D)/(NM)/(L)/*) map to null — NEVER 0 (a genuine 0 stays 0). unitMult (a power-of-10 multiplier) and unitOfMeasure are reported ALONGSIDE the raw dataValue — the value is NOT multiplied in (apply unitMult yourself). BEA returns the COMPLETE set for the filter (no pagination) ⇒ totalAvailable = the row count, complete:true; a genuine empty Data:[] ⇒ honest empty (returned:0); a 5xx ⇒ THROWS; a 200 non-JSON ⇒ schema_drift. The key rides ONLY in the UserID= query param — never logged or echoed.",
4372
4997
  inputSchema: BeaRegionalDataInput,
4373
4998
  handler: (input) => bea.regionalData(input),
4374
4999
  }),
@@ -4400,7 +5025,7 @@ export const TOOLS = [
4400
5025
  }),
4401
5026
  defineTool({
4402
5027
  name: "dol_get_dataset",
4403
- description: "Fetch records from ONE US DOL dataset (apiprod.dol.gov /v4/get/{agency}/{endpoint}/json). ★REQUIRES a free DOL_API_KEY: the DOL DATA endpoint has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://dol.gov/developer; the dataset CATALOG — dol_list_datasets — and agency list stay keyless). Input: `agency` (required — the `agencyAbbr` from dol_list_datasets, e.g. 'WHD', 'OSHA', 'ILAB'; rides the PATH, ^[A-Za-z0-9_]+$), `table` (required — the dataset's `apiUrl` endpoint from dol_list_datasets, e.g. 'Child_Labor_Report__2016_to_2022'; rides the PATH, ^[A-Za-z0-9_]+$), optional `limit` (default 10, max 100), `offset`, `filterField`+`filterValue` (a paired equality filter → a DOL filter_object), `fields` (best-effort column selection). Returns { records:[…verbatim dataset rows…] } + honest _meta. HONESTY: records are surfaced VERBATIM (the data-record envelope is key-gated and unverified, so field names/values are preserved as-is — a genuine 0 stays 0, a missing field stays null; the tool never coerces or fabricates). totalAvailable is a real count field ONLY when the response carries one, else null (an honest unknown — `returned` is NEVER passed off as the total); offset pagination (a full page ⇒ hasMore, page forward to confirm). A missing/invalid key (401/403) ⇒ invalid_input carrying the DOL_API_KEY guidance (never empty); a 400 ⇒ invalid_input; a genuine empty ⇒ honest empty (returned:0); a 429 ⇒ rate_limited THROWS (Retry-After honored); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / no row array ⇒ schema_drift. The key rides ONLY in the X-API-KEY request header — never the URL / _meta / a log.",
5028
+ description: "Fetch records from ONE US DOL dataset (apiprod.dol.gov /v4/get/{agency}/{endpoint}/json). ★REQUIRES a free DOL_API_KEY: the DOL DATA endpoint has NO keyless tier, so without the key this tool THROWS an honest config error (get one at https://dataportal.dol.gov/registration; the dataset CATALOG — dol_list_datasets — and agency list stay keyless). Input: `agency` (required — the `agencyAbbr` from dol_list_datasets, e.g. 'WHD', 'OSHA', 'ILAB'; rides the PATH, ^[A-Za-z0-9_]+$), `table` (required — the dataset's `apiUrl` endpoint from dol_list_datasets, e.g. 'Child_Labor_Report__2016_to_2022'; rides the PATH, ^[A-Za-z0-9_]+$), optional `limit` (default 10, max 100), `offset`, `filterField`+`filterValue` (a paired equality filter → a DOL filter_object), `fields` (best-effort column selection). Returns { records:[…verbatim dataset rows…] } + honest _meta. HONESTY: records are surfaced VERBATIM (the data-record envelope is key-gated and unverified, so field names/values are preserved as-is — a genuine 0 stays 0, a missing field stays null; the tool never coerces or fabricates). totalAvailable is a real count field ONLY when the response carries one, else null (an honest unknown — `returned` is NEVER passed off as the total); offset pagination (a full page ⇒ hasMore, page forward to confirm). A missing/invalid key (401/403) ⇒ invalid_input carrying the DOL_API_KEY guidance (never empty); a 400 ⇒ invalid_input; a genuine empty ⇒ honest empty (returned:0); a 429 ⇒ rate_limited THROWS (Retry-After honored); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / no row array ⇒ schema_drift. The key rides ONLY in the X-API-KEY request header — never the URL / _meta / a log.",
4404
5029
  inputSchema: DolGetDatasetInput,
4405
5030
  handler: (input) => dol.getDataset(input),
4406
5031
  }),
@@ -4417,6 +5042,40 @@ export const TOOLS = [
4417
5042
  inputSchema: LdaSearchFilingsInput,
4418
5043
  handler: (input) => lda.searchFilings(input),
4419
5044
  }),
5045
+ // ━━━ US federal court opinions (www.courtlistener.com) — the litigation lane (1) ━━━ ADR-0055
5046
+ // Federal court decisions — the judicial signal no contract/spending/lobbying source
5047
+ // carries (uscfc bid-protest/contract-claim opinions, cafc contract/patent appeals).
5048
+ // ★PROVENANCE: CourtListener (Free Law Project, a non-profit), NOT a .gov API — PACER
5049
+ // (the .gov source) is paywalled. KEYLESS (anonymous 200); the OPTIONAL free
5050
+ // COURTLISTENER_API_TOKEN only raises the rate limit, riding the Authorization: Token …
5051
+ // header ONLY. ★count is the API's REAL total — never results.length; CURSOR pagination
5052
+ // (nextCursor extracted from `next`, host re-asserted). type=o FIXED.
5053
+ defineTool({
5054
+ name: "courtlistener_search_opinions",
5055
+ description: "Search US FEDERAL COURT OPINIONS (case law / litigation) via CourtListener (www.courtlistener.com/api/rest/v4/search, type=o). ★PROVENANCE: the DATA is US federal court PUBLIC RECORDS, but the API is CourtListener, run by the Free Law Project (a NON-PROFIT) — this is NOT a .gov API; CourtListener republishes these records KEYLESS because the .gov primary source (PACER) is PAYWALLED. KEYLESS (anonymous access works; an optional free COURTLISTENER_API_TOKEN only raises the rate limit; get one at https://www.courtlistener.com/help/api/rest/; call api_key_status to see every source's key requirement). All inputs optional: `query` (full-text → q), `court` (a court id, ^[a-z0-9]+$ — e.g. 'uscfc' US Court of Federal Claims for contract claims/bid protests, 'cafc' Federal Circuit for contract/patent appeals, 'scotus'), `dateFiledAfter`/`dateFiledBefore` (ISO ^\\d{4}-\\d{2}-\\d{2}$ → filed_after/filed_before), `natureOfSuit` (folded into the q query — no verified dedicated filter, disclosed in notes), `cursor` (opaque continuation — pass back _meta.nextCursor), `order` (→ order_by, default 'dateFiled desc'). Returns { opinions:[{ caseName, court, courtId, dateFiled, docketNumber, natureOfSuit, status, judge, citation, absoluteUrl }] } + honest _meta. HONESTY: totalAvailable is the API's REAL `count` (the total match count for the filter) — NOT the rows on this page; pagination is an OPAQUE CURSOR (offset/nextOffset are null/meaningless — pass _meta.nextCursor back as `cursor`; nextCursor:null/hasMore:false = last page). CourtListener v4 stops counting on deep cursor pages (count:null) ⇒ totalAvailable:null is DISCLOSED, never faked as results.length. dateFiled is a date STRING; citation may be an array/object ⇒ flattened to a safe string/string[] (never fabricated); judge/natureOfSuit/docketNumber are null when absent (never ''); absoluteUrl is the full https://www.courtlistener.com link. A genuine no-match (results:[]) ⇒ honest empty (returned:0); a 400 (bad param) ⇒ invalid_input surfacing the API's message; a 429 (unauth throttle) ⇒ rate_limited THROWS (Retry-After honored, never routed around); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / non-array results / a count that is neither a number nor null ⇒ schema_drift; an off-host `next` is REFUSED (SSRF). The optional token rides ONLY in the Authorization: Token header (never the URL/_meta).",
5056
+ inputSchema: CourtlistenerSearchOpinionsInput,
5057
+ handler: (input) => courtlistener.searchOpinions(input),
5058
+ }),
5059
+ // ━━━ US tax-exempt nonprofits (projects.propublica.org) — the nonprofit lane (2) ━━━ ADR-0060
5060
+ // Who a 501(c) org IS (EIN, NTEE, subsection, ruling date, status) + its Form 990
5061
+ // FINANCIALS (revenue/expenses/assets/liabilities by year) — the nonprofit/grantee/
5062
+ // subcontractor vetting signal no contract/spending/grant/lobbying source carries.
5063
+ // ★PROVENANCE: IRS Form 990 public records republished KEYLESS by ProPublica Nonprofit
5064
+ // Explorer (a non-profit newsroom), NOT a .gov API (the IRS has no clean query API).
5065
+ // KEYLESS (no key). search total_results is the REAL total — never organizations.length;
5066
+ // financials totalAvailable = filings.length (the complete set). Money via num (null-never-0).
5067
+ defineTool({
5068
+ name: "nonprofit_search",
5069
+ description: "Search US TAX-EXEMPT NONPROFITS (501(c) organizations) by IRS Form 990 data via ProPublica Nonprofit Explorer (projects.propublica.org/nonprofits/api/v2/search). ★PROVENANCE: the DATA is IRS Form 990 filings — FEDERAL tax-exempt PUBLIC RECORDS — but the API is ProPublica Nonprofit Explorer, run by ProPublica (a NON-PROFIT newsroom) — this is NOT a .gov API; ProPublica republishes these records KEYLESS because the IRS itself has no clean query API (only bulk downloads / a web UI). KEYLESS (no key of any kind). All inputs optional: `query` (full-text org name/keyword → q), `state` (2-letter code → state[id], ^[A-Za-z]{2}$), `ntee` (NTEE major category, integer 1..10 → ntee[id]), `page` (0-BASED, default 0). Returns { organizations:[{ ein, name, city, state, nteeCode, subsectionCode }] } + honest _meta. HONESTY: totalAvailable is the API's REAL total_results (the total match count for the query) — NOT the organizations on this page; pagination is page-based and 0-INDEXED (pass page=cur_page+1 when hasMore). ein/nteeCode/subsectionCode are strings (never num-coerced). A genuine no-match (organizations:[]) ⇒ honest empty (returned:0, complete:true); a 4xx ⇒ invalid_input; a 429 ⇒ rate_limited THROWS (Retry-After honored, never routed around); a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / non-array organizations / non-number total_results ⇒ schema_drift. Data is IRS Form 990 data via ProPublica Nonprofit Explorer, disclosed in _meta.source and a note.",
5070
+ inputSchema: NonprofitSearchInput,
5071
+ handler: (input) => nonprofit.search(input),
5072
+ }),
5073
+ defineTool({
5074
+ name: "nonprofit_financials",
5075
+ description: "Fetch ONE US tax-exempt nonprofit's IRS Form 990 profile + FINANCIALS by EIN via ProPublica Nonprofit Explorer (projects.propublica.org/nonprofits/api/v2/organizations/{ein}.json). ★PROVENANCE: the DATA is IRS Form 990 filings (federal tax-exempt public records) but the API is ProPublica Nonprofit Explorer, run by ProPublica (a NON-PROFIT newsroom) — NOT a .gov API; ProPublica republishes these records KEYLESS because the IRS has no clean query API. KEYLESS (no key). Input: `ein` (required — the Employer Identification Number, 1..9 digits ^\\d{1,9}$, e.g. '530196605' American National Red Cross; rides the URL path). Returns { organization:{ ein, name, address, city, state, zip, nteeCode, subsectionCode, rulingDate, statusCode }, filings:[{ taxYear, formType, revenueUsd, expensesUsd, assetsUsd, liabilitiesUsd, pdfUrl }] } + honest _meta. HONESTY: the four Form 990 figures (revenueUsd/expensesUsd/assetsUsd/liabilitiesUsd, from totrevenue/totfuncexpns/totassetsend/totliabend) ride null-never-0 coercion — a genuine reported 0 stays 0, an absent figure ⇒ null (NEVER 0-faked); ein/codes are strings; rulingDate is a date string. totalAvailable = filings.length (the COMPLETE Form 990 filing set from the one detail document — no pagination). An unknown EIN (HTTP 404) ⇒ not_found (NEVER a fabricated empty org); a 4xx ⇒ invalid_input; a 429 ⇒ rate_limited THROWS; a 5xx/timeout ⇒ upstream_unavailable THROWS; a 200 non-JSON / non-object organization / non-array filings_with_data ⇒ schema_drift. Data is IRS Form 990 data via ProPublica Nonprofit Explorer, disclosed in _meta.source and a note.",
5076
+ inputSchema: NonprofitFinancialsInput,
5077
+ handler: (input) => nonprofit.financials(input),
5078
+ }),
4420
5079
  // ━━━ Self-service key discovery (1) ━━━
4421
5080
  // KEYLESS. A local status query — reads process.env (+ any .env auto-loaded at
4422
5081
  // startup) and reports, per key, whether it is set (a BOOLEAN — the key VALUE is