@cliwant/mcp-sam-gov 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +184 -0
  3. package/README.ko.md +184 -0
  4. package/README.md +397 -0
  5. package/dist/ecfr.d.ts +44 -0
  6. package/dist/ecfr.d.ts.map +1 -0
  7. package/dist/ecfr.js +86 -0
  8. package/dist/ecfr.js.map +1 -0
  9. package/dist/federal-register.d.ts +82 -0
  10. package/dist/federal-register.d.ts.map +1 -0
  11. package/dist/federal-register.js +117 -0
  12. package/dist/federal-register.js.map +1 -0
  13. package/dist/grants.d.ts +63 -0
  14. package/dist/grants.d.ts.map +1 -0
  15. package/dist/grants.js +93 -0
  16. package/dist/grants.js.map +1 -0
  17. package/dist/sam-gov/client.d.ts +69 -0
  18. package/dist/sam-gov/client.d.ts.map +1 -0
  19. package/dist/sam-gov/client.js +401 -0
  20. package/dist/sam-gov/client.js.map +1 -0
  21. package/dist/sam-gov/index.d.ts +19 -0
  22. package/dist/sam-gov/index.d.ts.map +1 -0
  23. package/dist/sam-gov/index.js +18 -0
  24. package/dist/sam-gov/index.js.map +1 -0
  25. package/dist/sam-gov/types.d.ts +109 -0
  26. package/dist/sam-gov/types.d.ts.map +1 -0
  27. package/dist/sam-gov/types.js +7 -0
  28. package/dist/sam-gov/types.js.map +1 -0
  29. package/dist/server.d.ts +20 -0
  30. package/dist/server.d.ts.map +1 -0
  31. package/dist/server.js +685 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/usaspending.d.ts +369 -0
  34. package/dist/usaspending.d.ts.map +1 -0
  35. package/dist/usaspending.js +555 -0
  36. package/dist/usaspending.js.map +1 -0
  37. package/package.json +88 -0
  38. package/src/ecfr.ts +127 -0
  39. package/src/federal-register.ts +191 -0
  40. package/src/grants.ts +155 -0
  41. package/src/sam-gov/client.ts +492 -0
  42. package/src/sam-gov/index.ts +28 -0
  43. package/src/sam-gov/types.ts +130 -0
  44. package/src/server.ts +856 -0
  45. package/src/usaspending.ts +925 -0
package/src/ecfr.ts ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * eCFR (Electronic Code of Federal Regulations) wrappers (keyless).
3
+ *
4
+ * eCFR is the up-to-date version of the CFR — Title 48 = FAR (Federal
5
+ * Acquisition Regulation), Title 2 = Federal financial assistance, etc.
6
+ * For a federal contractor, eCFR is the primary source for regulation
7
+ * text the agent should quote when answering compliance questions.
8
+ *
9
+ * Endpoints:
10
+ * - /versioner/v1/titles.json — list 50 CFR titles + last-amended dates
11
+ * - /search/v1/results — full-text search across the entire CFR
12
+ *
13
+ * Both keyless. Documented at https://www.ecfr.gov/developers/.
14
+ */
15
+
16
+ const ECFR = "https://www.ecfr.gov/api";
17
+
18
+ async function fetchJson<T>(url: string): Promise<T> {
19
+ const r = await fetch(url, {
20
+ headers: { Accept: "application/json" },
21
+ signal: AbortSignal.timeout(15_000),
22
+ });
23
+ if (!r.ok) {
24
+ throw new Error(`eCFR ${url} returned ${r.status}`);
25
+ }
26
+ return (await r.json()) as T;
27
+ }
28
+
29
+ export async function listTitles() {
30
+ type Resp = {
31
+ titles?: {
32
+ number?: number;
33
+ name?: string;
34
+ latest_amended_on?: string;
35
+ latest_issue_date?: string;
36
+ up_to_date_as_of?: string;
37
+ reserved?: boolean;
38
+ }[];
39
+ };
40
+ const json = await fetchJson<Resp>(`${ECFR}/versioner/v1/titles.json`);
41
+ return {
42
+ titles: (json.titles ?? []).map((t) => ({
43
+ number: t.number ?? 0,
44
+ name: t.name ?? "",
45
+ latestAmendedOn: t.latest_amended_on,
46
+ latestIssueDate: t.latest_issue_date,
47
+ upToDateAsOf: t.up_to_date_as_of,
48
+ reserved: !!t.reserved,
49
+ })),
50
+ };
51
+ }
52
+
53
+ export async function search(args: {
54
+ query: string;
55
+ titleNumber?: number;
56
+ perPage?: number;
57
+ }) {
58
+ const url = new URL(`${ECFR}/search/v1/results`);
59
+ url.searchParams.set("query", args.query);
60
+ url.searchParams.set("per_page", String(args.perPage ?? 5));
61
+ if (args.titleNumber) {
62
+ // eCFR search filter: hierarchy[title]=N (NOT just title=N — that's
63
+ // an "unpermitted parameter" error from the eCFR API).
64
+ url.searchParams.set("hierarchy[title]", String(args.titleNumber));
65
+ }
66
+
67
+ type Resp = {
68
+ results?: {
69
+ starts_on?: string;
70
+ ends_on?: string | null;
71
+ type?: string;
72
+ hierarchy?: {
73
+ title?: string;
74
+ chapter?: string;
75
+ subchapter?: string;
76
+ part?: string;
77
+ subpart?: string;
78
+ section?: string;
79
+ };
80
+ hierarchy_headings?: Record<string, string | null>;
81
+ headings?: Record<string, string | null>;
82
+ full_text_excerpt?: string;
83
+ score?: number;
84
+ }[];
85
+ };
86
+ const json = await fetchJson<Resp>(url.toString());
87
+ return {
88
+ results: (json.results ?? []).map((r) => ({
89
+ type: r.type ?? "",
90
+ title: r.hierarchy?.title ?? "",
91
+ chapter: r.hierarchy?.chapter,
92
+ part: r.hierarchy?.part,
93
+ subpart: r.hierarchy?.subpart,
94
+ section: r.hierarchy?.section,
95
+ headingPath: Object.values(r.hierarchy_headings ?? {})
96
+ .filter(Boolean)
97
+ .join(" › "),
98
+ excerpt: stripHtml(r.full_text_excerpt ?? ""),
99
+ score: r.score ?? 0,
100
+ // Stable ecfr.gov URL pattern from the hierarchy
101
+ ecfrUrl: r.hierarchy
102
+ ? buildEcfrUrl(r.hierarchy)
103
+ : "",
104
+ effectiveOn: r.starts_on ?? "",
105
+ })),
106
+ };
107
+ }
108
+
109
+ function stripHtml(s: string): string {
110
+ return s
111
+ .replace(/<[^>]+>/g, "")
112
+ .replace(/\s+/g, " ")
113
+ .trim();
114
+ }
115
+
116
+ function buildEcfrUrl(h: {
117
+ title?: string;
118
+ chapter?: string;
119
+ part?: string;
120
+ section?: string;
121
+ }): string {
122
+ const base = `https://www.ecfr.gov/current/title-${h.title}`;
123
+ if (h.section) return `${base}/section-${h.section}`;
124
+ if (h.part) return `${base}/part-${h.part}`;
125
+ if (h.chapter) return `${base}/chapter-${h.chapter}`;
126
+ return base;
127
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Federal Register API v1 wrappers (keyless, no registration).
3
+ *
4
+ * Federal Register is the daily journal of the US federal government —
5
+ * proposed rules, final rules, presidential documents, public notices.
6
+ * Critical context for any federal contracting question that touches
7
+ * regulation, set-aside policy, or new acquisition guidance.
8
+ *
9
+ * Endpoints:
10
+ * - documents.json — search across documents (filters: agencies,
11
+ * conditions, type, date range)
12
+ * - documents/{number}.json — single document detail (full body URL,
13
+ * abstract, citation, effective date)
14
+ * - agencies.json — agency reference list
15
+ *
16
+ * All endpoints are public + keyless (no API key, no registration).
17
+ * Rate-limit: documented as ~1000 req/hour per IP (informal).
18
+ */
19
+
20
+ const FED_REG = "https://www.federalregister.gov/api/v1";
21
+
22
+ async function fetchJson<T>(url: string): Promise<T> {
23
+ const r = await fetch(url, {
24
+ headers: { Accept: "application/json" },
25
+ signal: AbortSignal.timeout(15_000),
26
+ });
27
+ if (!r.ok) {
28
+ throw new Error(`Federal Register ${url} returned ${r.status}`);
29
+ }
30
+ return (await r.json()) as T;
31
+ }
32
+
33
+ export type FedRegDocumentType =
34
+ | "RULE"
35
+ | "PRORULE"
36
+ | "NOTICE"
37
+ | "PRESDOCU"
38
+ | "UNKNOWN";
39
+
40
+ const TYPE_MAP: Record<string, FedRegDocumentType> = {
41
+ Rule: "RULE",
42
+ "Proposed Rule": "PRORULE",
43
+ Notice: "NOTICE",
44
+ "Presidential Document": "PRESDOCU",
45
+ };
46
+
47
+ export async function searchDocuments(args: {
48
+ query?: string;
49
+ agencySlugs?: string[]; // e.g. ["veterans-affairs-department", "defense-department"]
50
+ type?: "RULE" | "PRORULE" | "NOTICE" | "PRESDOCU";
51
+ publicationDateFrom?: string; // YYYY-MM-DD
52
+ publicationDateTo?: string;
53
+ effectiveDateFrom?: string;
54
+ perPage?: number;
55
+ }) {
56
+ const url = new URL(`${FED_REG}/documents.json`);
57
+ url.searchParams.set("per_page", String(args.perPage ?? 10));
58
+ if (args.query) {
59
+ url.searchParams.set("conditions[term]", args.query);
60
+ }
61
+ for (const slug of args.agencySlugs ?? []) {
62
+ url.searchParams.append("conditions[agencies][]", slug);
63
+ }
64
+ if (args.type) {
65
+ url.searchParams.append("conditions[type][]", args.type);
66
+ }
67
+ if (args.publicationDateFrom) {
68
+ url.searchParams.set(
69
+ "conditions[publication_date][gte]",
70
+ args.publicationDateFrom,
71
+ );
72
+ }
73
+ if (args.publicationDateTo) {
74
+ url.searchParams.set(
75
+ "conditions[publication_date][lte]",
76
+ args.publicationDateTo,
77
+ );
78
+ }
79
+ if (args.effectiveDateFrom) {
80
+ url.searchParams.set(
81
+ "conditions[effective_date][gte]",
82
+ args.effectiveDateFrom,
83
+ );
84
+ }
85
+
86
+ type Resp = {
87
+ count?: number;
88
+ total_pages?: number;
89
+ results?: {
90
+ title?: string;
91
+ type?: string;
92
+ abstract?: string;
93
+ document_number?: string;
94
+ html_url?: string;
95
+ pdf_url?: string;
96
+ publication_date?: string;
97
+ effective_on?: string;
98
+ agencies?: { name?: string; slug?: string }[];
99
+ }[];
100
+ };
101
+ const json = await fetchJson<Resp>(url.toString());
102
+ return {
103
+ totalRecords: json.count ?? 0,
104
+ totalPages: json.total_pages ?? 0,
105
+ documents: (json.results ?? []).map((d) => ({
106
+ documentNumber: d.document_number ?? "",
107
+ title: d.title ?? "",
108
+ type: TYPE_MAP[d.type ?? ""] ?? "UNKNOWN",
109
+ typeDisplay: d.type ?? "",
110
+ abstract: d.abstract ?? "",
111
+ htmlUrl: d.html_url ?? "",
112
+ pdfUrl: d.pdf_url,
113
+ publicationDate: d.publication_date ?? "",
114
+ effectiveDate: d.effective_on,
115
+ agencies: (d.agencies ?? []).map((a) => ({
116
+ name: a.name ?? "",
117
+ slug: a.slug ?? "",
118
+ })),
119
+ })),
120
+ };
121
+ }
122
+
123
+ export async function getDocument(documentNumber: string) {
124
+ type Resp = {
125
+ title?: string;
126
+ type?: string;
127
+ abstract?: string;
128
+ document_number?: string;
129
+ html_url?: string;
130
+ pdf_url?: string;
131
+ body_html_url?: string;
132
+ publication_date?: string;
133
+ effective_on?: string;
134
+ citation?: string;
135
+ page_length?: number;
136
+ raw_text_url?: string;
137
+ agencies?: { name?: string; slug?: string }[];
138
+ cfr_references?: { title?: string; part?: string; chapter?: string }[];
139
+ };
140
+ const json = await fetchJson<Resp>(
141
+ `${FED_REG}/documents/${encodeURIComponent(documentNumber)}.json`,
142
+ );
143
+ return {
144
+ documentNumber: json.document_number ?? "",
145
+ title: json.title ?? "",
146
+ type: TYPE_MAP[json.type ?? ""] ?? "UNKNOWN",
147
+ typeDisplay: json.type ?? "",
148
+ abstract: json.abstract ?? "",
149
+ htmlUrl: json.html_url ?? "",
150
+ pdfUrl: json.pdf_url,
151
+ rawTextUrl: json.raw_text_url,
152
+ publicationDate: json.publication_date ?? "",
153
+ effectiveDate: json.effective_on,
154
+ citation: json.citation,
155
+ pageCount: json.page_length,
156
+ agencies: (json.agencies ?? []).map((a) => ({
157
+ name: a.name ?? "",
158
+ slug: a.slug ?? "",
159
+ })),
160
+ cfrReferences: (json.cfr_references ?? []).map((c) => ({
161
+ title: c.title ?? "",
162
+ part: c.part,
163
+ chapter: c.chapter,
164
+ })),
165
+ };
166
+ }
167
+
168
+ export async function listAgencies(args: { perPage?: number }) {
169
+ type Resp = Array<{
170
+ id?: number;
171
+ name?: string;
172
+ short_name?: string;
173
+ slug?: string;
174
+ description?: string;
175
+ parent_id?: number | null;
176
+ json_url?: string;
177
+ }>;
178
+ const json = await fetchJson<Resp>(
179
+ `${FED_REG}/agencies.json?per_page=${args.perPage ?? 100}`,
180
+ );
181
+ return {
182
+ agencies: (json ?? []).map((a) => ({
183
+ id: a.id ?? 0,
184
+ name: a.name ?? "",
185
+ shortName: a.short_name,
186
+ slug: a.slug ?? "",
187
+ description: a.description ?? "",
188
+ parentId: a.parent_id,
189
+ })),
190
+ };
191
+ }
package/src/grants.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Grants.gov v1 API wrappers (keyless).
3
+ *
4
+ * Grants.gov hosts federal financial-assistance opportunities (grants,
5
+ * cooperative agreements). Distinct from SAM.gov contracts but the
6
+ * same pursuit ICP often cares about both.
7
+ *
8
+ * Endpoints (POST JSON, no key):
9
+ * - /v1/api/search2 — search opportunities
10
+ * - /v1/api/fetchOpportunity — single grant detail
11
+ *
12
+ * Documented at https://grants.gov/web/grants/s2s/grantor/schemas/grants-search-2-soap.html
13
+ */
14
+
15
+ const GRANTS = "https://api.grants.gov/v1/api";
16
+
17
+ async function postJson<T>(
18
+ endpoint: string,
19
+ body: Record<string, unknown>,
20
+ ): Promise<T> {
21
+ const r = await fetch(`${GRANTS}/${endpoint}`, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify(body),
25
+ signal: AbortSignal.timeout(15_000),
26
+ });
27
+ if (!r.ok) {
28
+ throw new Error(`Grants.gov ${endpoint} returned ${r.status}`);
29
+ }
30
+ return (await r.json()) as T;
31
+ }
32
+
33
+ export type GrantStatus = "forecasted" | "posted" | "closed" | "archived";
34
+
35
+ export async function searchGrants(args: {
36
+ keyword?: string;
37
+ cfda?: string; // CFDA program number, e.g. "10.500"
38
+ agency?: string; // agency code, e.g. "DHS-FEMA"
39
+ oppNum?: string; // opportunity number
40
+ oppStatuses?: GrantStatus[];
41
+ rows?: number;
42
+ }) {
43
+ const body: Record<string, unknown> = {
44
+ rows: args.rows ?? 10,
45
+ keyword: args.keyword ?? "",
46
+ cfda: args.cfda ?? "",
47
+ agencies: args.agency ?? "",
48
+ oppNum: args.oppNum ?? "",
49
+ oppStatuses: (args.oppStatuses ?? ["forecasted", "posted"]).join("|"),
50
+ };
51
+ type Resp = {
52
+ errorcode?: number;
53
+ msg?: string;
54
+ data?: {
55
+ hitCount?: number;
56
+ oppHits?: {
57
+ id?: string;
58
+ number?: string;
59
+ title?: string;
60
+ agencyCode?: string;
61
+ agencyName?: string;
62
+ openDate?: string;
63
+ closeDate?: string;
64
+ oppStatus?: string;
65
+ docType?: string;
66
+ cfdaList?: string;
67
+ }[];
68
+ };
69
+ };
70
+ const json = await postJson<Resp>("search2", body);
71
+ if (json.errorcode && json.errorcode !== 0) {
72
+ throw new Error(`Grants.gov error: ${json.msg ?? "unknown"}`);
73
+ }
74
+ return {
75
+ totalRecords: json.data?.hitCount ?? 0,
76
+ grants: (json.data?.oppHits ?? []).map((g) => ({
77
+ id: g.id ?? "",
78
+ opportunityNumber: g.number ?? "",
79
+ title: g.title ?? "",
80
+ agencyCode: g.agencyCode ?? "",
81
+ agencyName: g.agencyName ?? "",
82
+ openDate: g.openDate,
83
+ closeDate: g.closeDate,
84
+ status: g.oppStatus,
85
+ docType: g.docType,
86
+ cfdaList: g.cfdaList,
87
+ })),
88
+ };
89
+ }
90
+
91
+ export async function getGrant(args: { opportunityId: string }) {
92
+ type Resp = {
93
+ errorcode?: number;
94
+ msg?: string;
95
+ data?: {
96
+ id?: number;
97
+ opportunityNumber?: string;
98
+ opportunityTitle?: string;
99
+ owningAgencyCode?: string;
100
+ synopsisDesc?: string;
101
+ synopsis?: {
102
+ synopsisDesc?: string;
103
+ applicantTypes?: { description?: string }[];
104
+ fundingActivityCategories?: { description?: string }[];
105
+ fundingInstruments?: { description?: string }[];
106
+ responseDate?: string;
107
+ postingDate?: string;
108
+ archiveDate?: string;
109
+ awardCeiling?: number;
110
+ awardFloor?: number;
111
+ estimatedFunding?: number;
112
+ expectedNumberOfAwards?: number;
113
+ agencyName?: string;
114
+ agencyCode?: string;
115
+ };
116
+ opportunityHistoryDetails?: { actionType?: string; actionDate?: string }[];
117
+ cfdas?: { cfdaNumber?: string; programTitle?: string }[];
118
+ };
119
+ };
120
+ const json = await postJson<Resp>("fetchOpportunity", {
121
+ opportunityId: args.opportunityId,
122
+ });
123
+ if (json.errorcode && json.errorcode !== 0) {
124
+ throw new Error(`Grants.gov error: ${json.msg ?? "unknown"}`);
125
+ }
126
+ const d = json.data ?? {};
127
+ const s = d.synopsis ?? {};
128
+ return {
129
+ id: d.id ?? 0,
130
+ opportunityNumber: d.opportunityNumber ?? "",
131
+ title: d.opportunityTitle ?? "",
132
+ agency: { code: s.agencyCode ?? d.owningAgencyCode, name: s.agencyName },
133
+ description: s.synopsisDesc ?? d.synopsisDesc ?? "",
134
+ postingDate: s.postingDate,
135
+ responseDate: s.responseDate,
136
+ archiveDate: s.archiveDate,
137
+ awardCeiling: s.awardCeiling,
138
+ awardFloor: s.awardFloor,
139
+ estimatedFunding: s.estimatedFunding,
140
+ expectedNumberOfAwards: s.expectedNumberOfAwards,
141
+ applicantTypes: (s.applicantTypes ?? [])
142
+ .map((a) => a.description)
143
+ .filter(Boolean) as string[],
144
+ fundingInstruments: (s.fundingInstruments ?? [])
145
+ .map((f) => f.description)
146
+ .filter(Boolean) as string[],
147
+ fundingCategories: (s.fundingActivityCategories ?? [])
148
+ .map((f) => f.description)
149
+ .filter(Boolean) as string[],
150
+ cfdaPrograms: (d.cfdas ?? []).map((c) => ({
151
+ number: c.cfdaNumber ?? "",
152
+ title: c.programTitle ?? "",
153
+ })),
154
+ };
155
+ }