@cliwant/mcp-sam-gov 1.5.0 → 1.7.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 (89) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +248 -231
  3. package/README.ko.md +248 -231
  4. package/README.md +733 -714
  5. package/dist/errors.d.ts +10 -0
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/errors.js.map +1 -1
  8. package/dist/feedback.d.ts +64 -0
  9. package/dist/feedback.d.ts.map +1 -0
  10. package/dist/feedback.js +131 -0
  11. package/dist/feedback.js.map +1 -0
  12. package/dist/server.d.ts.map +1 -1
  13. package/dist/server.js +48 -2
  14. package/dist/server.js.map +1 -1
  15. package/dist/update-check.d.ts +38 -0
  16. package/dist/update-check.d.ts.map +1 -0
  17. package/dist/update-check.js +85 -0
  18. package/dist/update-check.js.map +1 -0
  19. package/package.json +111 -111
  20. package/src/attachments.ts +652 -652
  21. package/src/bea.ts +372 -372
  22. package/src/bls.ts +1943 -1943
  23. package/src/cache.ts +73 -73
  24. package/src/cbp-border.ts +177 -177
  25. package/src/census-economic.ts +431 -431
  26. package/src/census.ts +735 -735
  27. package/src/ckan.ts +495 -495
  28. package/src/clinicaltrials.ts +923 -923
  29. package/src/cms-facility.ts +379 -379
  30. package/src/cms-hospital.ts +344 -344
  31. package/src/cms-supplier.ts +527 -527
  32. package/src/cms-utilization.ts +389 -389
  33. package/src/cms.ts +634 -634
  34. package/src/coerce.ts +47 -47
  35. package/src/courtlistener.ts +465 -465
  36. package/src/cpsc.ts +333 -333
  37. package/src/datagov-catalog.ts +312 -312
  38. package/src/datagov.ts +907 -907
  39. package/src/datagovKey.ts +68 -68
  40. package/src/datasource.ts +721 -721
  41. package/src/disclosure.ts +61 -61
  42. package/src/dol.ts +515 -515
  43. package/src/ecfr.ts +248 -248
  44. package/src/echo.ts +496 -496
  45. package/src/edgar.ts +3046 -3046
  46. package/src/epa-envirofacts.ts +358 -358
  47. package/src/errors.ts +324 -314
  48. package/src/fac.ts +529 -529
  49. package/src/far.ts +1009 -1009
  50. package/src/fdic.ts +2052 -2052
  51. package/src/federal-register.ts +725 -725
  52. package/src/feedback.ts +160 -0
  53. package/src/fema.ts +680 -680
  54. package/src/fpds.ts +620 -620
  55. package/src/fred.ts +464 -464
  56. package/src/gao.ts +744 -744
  57. package/src/gov-domains.ts +237 -237
  58. package/src/govinfo.ts +497 -497
  59. package/src/grants.ts +290 -290
  60. package/src/gsa-csv.ts +992 -992
  61. package/src/gsa-perdiem.ts +361 -361
  62. package/src/integrity.ts +928 -928
  63. package/src/keys.ts +268 -268
  64. package/src/lda.ts +385 -385
  65. package/src/meta.ts +292 -292
  66. package/src/nhtsa.ts +352 -352
  67. package/src/nih.ts +375 -375
  68. package/src/nist-controls.ts +219 -219
  69. package/src/nonprofit.ts +460 -460
  70. package/src/nppes.ts +834 -834
  71. package/src/nsf.ts +706 -706
  72. package/src/nvd.ts +1124 -1124
  73. package/src/nws-weather.ts +167 -167
  74. package/src/ofac.ts +1166 -1166
  75. package/src/openfda-device.ts +356 -356
  76. package/src/openfda-drugsfda.ts +313 -313
  77. package/src/openfda.ts +518 -518
  78. package/src/pricing.ts +1075 -1075
  79. package/src/sam-gov/client.ts +774 -774
  80. package/src/sam-gov/index.ts +32 -32
  81. package/src/sam-gov/types.ts +152 -152
  82. package/src/sba.ts +357 -357
  83. package/src/server.ts +6692 -6639
  84. package/src/snapshot.ts +223 -223
  85. package/src/socrata.ts +532 -532
  86. package/src/treasury.ts +582 -582
  87. package/src/update-check.ts +88 -0
  88. package/src/usaspending.ts +2852 -2852
  89. package/src/usitc.ts +420 -420
@@ -1,167 +1,167 @@
1
- /**
2
- * nws-weather.ts — National Weather Service active alerts (api.weather.gov, KEYLESS)
3
- * — the DISASTER / CLIMATE-READINESS lane. Current watches, warnings, and advisories
4
- * (event, severity, urgency, area, effective/expires window, instructions). Pairs
5
- * directly with the FEMA tools (disaster declarations → public assistance → hazard
6
- * mitigation → LIVE active weather) to complete a disaster-response-readiness view:
7
- * where severe-weather events are active NOW, ahead of the declarations/contracts
8
- * that follow.
9
- *
10
- * SOURCE: NWS api.weather.gov (a .gov host), keyless. Requires a descriptive
11
- * User-Agent (NWS policy) — sent from a fixed constant; no token. Returns a GeoJSON
12
- * FeatureCollection. This is REAL-TIME data (currently-active alerts) — freshness is
13
- * disclosed and never implied to be a historical series.
14
- *
15
- * HONESTY: fixed host + redirect:"error" (SSRF); `state` is a 2-letter charclass (the
16
- * server-side ?area= filter); event/severity are applied CLIENT-SIDE over the returned
17
- * set; a non-FeatureCollection / non-array body ⇒ driftError; an outage/4xx/timeout
18
- * THROWS. A genuine no-active-alerts result ⇒ an HONEST EMPTY (returned:0), never an
19
- * error. totalAvailable = the EXACT count of matched active alerts. Every scalar via
20
- * `str` (null-never-empty-string); dates preserved as ISO strings.
21
- */
22
-
23
- import { ToolErrorCarrier } from "./errors.js";
24
- import { getJson, driftError } from "./datasource.js";
25
- import { str } from "./coerce.js";
26
- import { withMeta, type MetaBundle, type ResponseMeta } from "./meta.js";
27
-
28
- export const NWS_HOST = "api.weather.gov";
29
- const NWS_ACTIVE_URL = "https://api.weather.gov/alerts/active";
30
- const NWS_LABEL = "nws:/alerts/active";
31
- const NWS_TIMEOUT_MS = 15_000;
32
- // NWS asks every client to send a descriptive User-Agent (with contact). No token.
33
- const NWS_USER_AGENT = "cliwant-mcp-sam-gov (https://github.com/cliwant/mcp-sam-gov)";
34
- const STATE_RE = /^[A-Za-z]{2}$/;
35
-
36
- const PROVENANCE_NOTE =
37
- "Source: NWS api.weather.gov active-alerts feed (keyless; a descriptive User-Agent is sent per NWS policy).";
38
- const FRESHNESS_NOTE =
39
- "REAL-TIME: these are the alerts ACTIVE at request time (a live snapshot, not a historical archive). Read effective/onset/expires/ends for each alert's window; an expired alert is not returned. A no-active-alerts result is an HONEST empty (returned:0), never an error.";
40
-
41
- export type NwsAlert = {
42
- id: string | null;
43
- event: string | null; // e.g. "Wind Advisory", "Flood Warning"
44
- headline: string | null;
45
- severity: string | null; // Extreme | Severe | Moderate | Minor | Unknown
46
- urgency: string | null; // Immediate | Expected | Future | Past | Unknown
47
- certainty: string | null;
48
- category: string | null;
49
- status: string | null; // Actual | Exercise | Test | …
50
- messageType: string | null; // Alert | Update | Cancel
51
- areaDesc: string | null;
52
- effective: string | null;
53
- onset: string | null;
54
- expires: string | null;
55
- ends: string | null;
56
- senderName: string | null;
57
- description: string | null;
58
- instruction: string | null;
59
- response: string | null;
60
- };
61
-
62
- function mapAlert(feature: unknown): NwsAlert {
63
- const p = ((feature ?? {}) as { properties?: Record<string, unknown> }).properties ?? {};
64
- return {
65
- id: str(p.id),
66
- event: str(p.event),
67
- headline: str(p.headline),
68
- severity: str(p.severity),
69
- urgency: str(p.urgency),
70
- certainty: str(p.certainty),
71
- category: str(p.category),
72
- status: str(p.status),
73
- messageType: str(p.messageType),
74
- areaDesc: str(p.areaDesc),
75
- effective: str(p.effective),
76
- onset: str(p.onset),
77
- expires: str(p.expires),
78
- ends: str(p.ends),
79
- senderName: str(p.senderName),
80
- description: str(p.description),
81
- instruction: str(p.instruction),
82
- response: str(p.response),
83
- };
84
- }
85
-
86
- async function loadActiveAlerts(state: string | undefined): Promise<NwsAlert[]> {
87
- const params = new URLSearchParams();
88
- if (state !== undefined) params.set("area", state.toUpperCase());
89
- const url = params.toString() ? `${NWS_ACTIVE_URL}?${params.toString()}` : NWS_ACTIVE_URL;
90
- const built = new URL(url);
91
- if (built.hostname !== NWS_HOST || built.protocol !== "https:") {
92
- throw driftError(NWS_LABEL, `Constructed NWS URL host ${JSON.stringify(built.hostname)} is not ${NWS_HOST} over https — refusing to fetch (SSRF safety).`);
93
- }
94
- const body = (await getJson(url, {
95
- label: NWS_LABEL,
96
- redirect: "error",
97
- timeoutMs: NWS_TIMEOUT_MS,
98
- headers: { "User-Agent": NWS_USER_AGENT, Accept: "application/geo+json" },
99
- })) as { type?: unknown; features?: unknown };
100
- if (body.type !== "FeatureCollection" || !Array.isArray(body.features)) {
101
- throw driftError(NWS_LABEL, "NWS active-alerts body is not a GeoJSON FeatureCollection with a features[] array — schema drift, never a fake-empty result.");
102
- }
103
- return (body.features as unknown[]).map(mapAlert);
104
- }
105
-
106
- // ─── Tool: nws_active_alerts ──────────────────────────────────────
107
- /**
108
- * List CURRENTLY-ACTIVE NWS weather alerts, optionally scoped by `state` (server-side
109
- * ?area=) and filtered client-side by `event` (substring) and/or `severity` (exact).
110
- * Honest `_meta` (exact match total + real-time freshness; a no-alerts result is an
111
- * honest empty).
112
- */
113
- export async function activeAlerts(args: {
114
- state?: string;
115
- event?: string;
116
- severity?: string;
117
- limit?: number;
118
- offset?: number;
119
- }): Promise<MetaBundle> {
120
- if (args.state !== undefined && !STATE_RE.test(args.state)) {
121
- throw new ToolErrorCarrier({
122
- kind: "invalid_input",
123
- retryable: false,
124
- message: `Invalid state ${JSON.stringify(args.state)} — expected a 2-letter US state/territory code (^[A-Za-z]{2}$), e.g. "CA".`,
125
- upstreamEndpoint: NWS_LABEL,
126
- });
127
- }
128
- const limit = args.limit ?? 50;
129
- const offset = args.offset ?? 0;
130
-
131
- const all = await loadActiveAlerts(args.state);
132
-
133
- const filtersApplied: string[] = [];
134
- if (args.state !== undefined) filtersApplied.push("state");
135
- const eventQ = args.event?.trim().toLowerCase();
136
- const sevQ = args.severity?.trim().toLowerCase();
137
- if (args.event !== undefined) filtersApplied.push("event");
138
- if (args.severity !== undefined) filtersApplied.push("severity");
139
-
140
- const matched = all.filter((a) => {
141
- if (eventQ && !(a.event ?? "").toLowerCase().includes(eventQ)) return false;
142
- if (sevQ && (a.severity ?? "").toLowerCase() !== sevQ) return false;
143
- return true;
144
- });
145
-
146
- const totalAvailable = matched.length;
147
- const page = matched.slice(offset, offset + limit);
148
- const returned = page.length;
149
- const hasMore = offset + returned < totalAvailable;
150
- const nextOffset = hasMore ? offset + returned : null;
151
-
152
- return withMeta(
153
- { alerts: page },
154
- {
155
- source: "api.weather.gov active alerts (NWS, keyless)",
156
- keylessMode: true,
157
- returned,
158
- totalAvailable,
159
- truncated: hasMore,
160
- filtersApplied,
161
- filtersDropped: [],
162
- fieldsUnavailable: [],
163
- pagination: { offset, limit, hasMore, nextOffset },
164
- notes: [PROVENANCE_NOTE, FRESHNESS_NOTE],
165
- } satisfies Partial<ResponseMeta>,
166
- );
167
- }
1
+ /**
2
+ * nws-weather.ts — National Weather Service active alerts (api.weather.gov, KEYLESS)
3
+ * — the DISASTER / CLIMATE-READINESS lane. Current watches, warnings, and advisories
4
+ * (event, severity, urgency, area, effective/expires window, instructions). Pairs
5
+ * directly with the FEMA tools (disaster declarations → public assistance → hazard
6
+ * mitigation → LIVE active weather) to complete a disaster-response-readiness view:
7
+ * where severe-weather events are active NOW, ahead of the declarations/contracts
8
+ * that follow.
9
+ *
10
+ * SOURCE: NWS api.weather.gov (a .gov host), keyless. Requires a descriptive
11
+ * User-Agent (NWS policy) — sent from a fixed constant; no token. Returns a GeoJSON
12
+ * FeatureCollection. This is REAL-TIME data (currently-active alerts) — freshness is
13
+ * disclosed and never implied to be a historical series.
14
+ *
15
+ * HONESTY: fixed host + redirect:"error" (SSRF); `state` is a 2-letter charclass (the
16
+ * server-side ?area= filter); event/severity are applied CLIENT-SIDE over the returned
17
+ * set; a non-FeatureCollection / non-array body ⇒ driftError; an outage/4xx/timeout
18
+ * THROWS. A genuine no-active-alerts result ⇒ an HONEST EMPTY (returned:0), never an
19
+ * error. totalAvailable = the EXACT count of matched active alerts. Every scalar via
20
+ * `str` (null-never-empty-string); dates preserved as ISO strings.
21
+ */
22
+
23
+ import { ToolErrorCarrier } from "./errors.js";
24
+ import { getJson, driftError } from "./datasource.js";
25
+ import { str } from "./coerce.js";
26
+ import { withMeta, type MetaBundle, type ResponseMeta } from "./meta.js";
27
+
28
+ export const NWS_HOST = "api.weather.gov";
29
+ const NWS_ACTIVE_URL = "https://api.weather.gov/alerts/active";
30
+ const NWS_LABEL = "nws:/alerts/active";
31
+ const NWS_TIMEOUT_MS = 15_000;
32
+ // NWS asks every client to send a descriptive User-Agent (with contact). No token.
33
+ const NWS_USER_AGENT = "cliwant-mcp-sam-gov (https://github.com/cliwant/mcp-sam-gov)";
34
+ const STATE_RE = /^[A-Za-z]{2}$/;
35
+
36
+ const PROVENANCE_NOTE =
37
+ "Source: NWS api.weather.gov active-alerts feed (keyless; a descriptive User-Agent is sent per NWS policy).";
38
+ const FRESHNESS_NOTE =
39
+ "REAL-TIME: these are the alerts ACTIVE at request time (a live snapshot, not a historical archive). Read effective/onset/expires/ends for each alert's window; an expired alert is not returned. A no-active-alerts result is an HONEST empty (returned:0), never an error.";
40
+
41
+ export type NwsAlert = {
42
+ id: string | null;
43
+ event: string | null; // e.g. "Wind Advisory", "Flood Warning"
44
+ headline: string | null;
45
+ severity: string | null; // Extreme | Severe | Moderate | Minor | Unknown
46
+ urgency: string | null; // Immediate | Expected | Future | Past | Unknown
47
+ certainty: string | null;
48
+ category: string | null;
49
+ status: string | null; // Actual | Exercise | Test | …
50
+ messageType: string | null; // Alert | Update | Cancel
51
+ areaDesc: string | null;
52
+ effective: string | null;
53
+ onset: string | null;
54
+ expires: string | null;
55
+ ends: string | null;
56
+ senderName: string | null;
57
+ description: string | null;
58
+ instruction: string | null;
59
+ response: string | null;
60
+ };
61
+
62
+ function mapAlert(feature: unknown): NwsAlert {
63
+ const p = ((feature ?? {}) as { properties?: Record<string, unknown> }).properties ?? {};
64
+ return {
65
+ id: str(p.id),
66
+ event: str(p.event),
67
+ headline: str(p.headline),
68
+ severity: str(p.severity),
69
+ urgency: str(p.urgency),
70
+ certainty: str(p.certainty),
71
+ category: str(p.category),
72
+ status: str(p.status),
73
+ messageType: str(p.messageType),
74
+ areaDesc: str(p.areaDesc),
75
+ effective: str(p.effective),
76
+ onset: str(p.onset),
77
+ expires: str(p.expires),
78
+ ends: str(p.ends),
79
+ senderName: str(p.senderName),
80
+ description: str(p.description),
81
+ instruction: str(p.instruction),
82
+ response: str(p.response),
83
+ };
84
+ }
85
+
86
+ async function loadActiveAlerts(state: string | undefined): Promise<NwsAlert[]> {
87
+ const params = new URLSearchParams();
88
+ if (state !== undefined) params.set("area", state.toUpperCase());
89
+ const url = params.toString() ? `${NWS_ACTIVE_URL}?${params.toString()}` : NWS_ACTIVE_URL;
90
+ const built = new URL(url);
91
+ if (built.hostname !== NWS_HOST || built.protocol !== "https:") {
92
+ throw driftError(NWS_LABEL, `Constructed NWS URL host ${JSON.stringify(built.hostname)} is not ${NWS_HOST} over https — refusing to fetch (SSRF safety).`);
93
+ }
94
+ const body = (await getJson(url, {
95
+ label: NWS_LABEL,
96
+ redirect: "error",
97
+ timeoutMs: NWS_TIMEOUT_MS,
98
+ headers: { "User-Agent": NWS_USER_AGENT, Accept: "application/geo+json" },
99
+ })) as { type?: unknown; features?: unknown };
100
+ if (body.type !== "FeatureCollection" || !Array.isArray(body.features)) {
101
+ throw driftError(NWS_LABEL, "NWS active-alerts body is not a GeoJSON FeatureCollection with a features[] array — schema drift, never a fake-empty result.");
102
+ }
103
+ return (body.features as unknown[]).map(mapAlert);
104
+ }
105
+
106
+ // ─── Tool: nws_active_alerts ──────────────────────────────────────
107
+ /**
108
+ * List CURRENTLY-ACTIVE NWS weather alerts, optionally scoped by `state` (server-side
109
+ * ?area=) and filtered client-side by `event` (substring) and/or `severity` (exact).
110
+ * Honest `_meta` (exact match total + real-time freshness; a no-alerts result is an
111
+ * honest empty).
112
+ */
113
+ export async function activeAlerts(args: {
114
+ state?: string;
115
+ event?: string;
116
+ severity?: string;
117
+ limit?: number;
118
+ offset?: number;
119
+ }): Promise<MetaBundle> {
120
+ if (args.state !== undefined && !STATE_RE.test(args.state)) {
121
+ throw new ToolErrorCarrier({
122
+ kind: "invalid_input",
123
+ retryable: false,
124
+ message: `Invalid state ${JSON.stringify(args.state)} — expected a 2-letter US state/territory code (^[A-Za-z]{2}$), e.g. "CA".`,
125
+ upstreamEndpoint: NWS_LABEL,
126
+ });
127
+ }
128
+ const limit = args.limit ?? 50;
129
+ const offset = args.offset ?? 0;
130
+
131
+ const all = await loadActiveAlerts(args.state);
132
+
133
+ const filtersApplied: string[] = [];
134
+ if (args.state !== undefined) filtersApplied.push("state");
135
+ const eventQ = args.event?.trim().toLowerCase();
136
+ const sevQ = args.severity?.trim().toLowerCase();
137
+ if (args.event !== undefined) filtersApplied.push("event");
138
+ if (args.severity !== undefined) filtersApplied.push("severity");
139
+
140
+ const matched = all.filter((a) => {
141
+ if (eventQ && !(a.event ?? "").toLowerCase().includes(eventQ)) return false;
142
+ if (sevQ && (a.severity ?? "").toLowerCase() !== sevQ) return false;
143
+ return true;
144
+ });
145
+
146
+ const totalAvailable = matched.length;
147
+ const page = matched.slice(offset, offset + limit);
148
+ const returned = page.length;
149
+ const hasMore = offset + returned < totalAvailable;
150
+ const nextOffset = hasMore ? offset + returned : null;
151
+
152
+ return withMeta(
153
+ { alerts: page },
154
+ {
155
+ source: "api.weather.gov active alerts (NWS, keyless)",
156
+ keylessMode: true,
157
+ returned,
158
+ totalAvailable,
159
+ truncated: hasMore,
160
+ filtersApplied,
161
+ filtersDropped: [],
162
+ fieldsUnavailable: [],
163
+ pagination: { offset, limit, hasMore, nextOffset },
164
+ notes: [PROVENANCE_NOTE, FRESHNESS_NOTE],
165
+ } satisfies Partial<ResponseMeta>,
166
+ );
167
+ }