@cliwant/mcp-sam-gov 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -5
- package/dist/cache.d.ts +40 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +60 -0
- package/dist/cache.js.map +1 -0
- package/dist/ecfr.d.ts.map +1 -1
- package/dist/ecfr.js +18 -16
- package/dist/ecfr.js.map +1 -1
- package/dist/errors.d.ts +76 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +179 -0
- package/dist/errors.js.map +1 -0
- package/dist/federal-register.d.ts.map +1 -1
- package/dist/federal-register.js +17 -16
- package/dist/federal-register.js.map +1 -1
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +3 -5
- package/dist/grants.js.map +1 -1
- package/dist/server.js +14 -5
- package/dist/server.js.map +1 -1
- package/dist/usaspending.d.ts.map +1 -1
- package/dist/usaspending.js +95 -87
- package/dist/usaspending.js.map +1 -1
- package/package.json +1 -1
- package/src/cache.ts +68 -0
- package/src/ecfr.ts +134 -127
- package/src/errors.ts +220 -0
- package/src/federal-register.ts +197 -191
- package/src/grants.ts +158 -155
- package/src/server.ts +14 -5
- package/src/usaspending.ts +948 -925
package/src/grants.ts
CHANGED
|
@@ -1,155 +1,158 @@
|
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.map((
|
|
146
|
-
.filter(Boolean) as string[],
|
|
147
|
-
|
|
148
|
-
.map((f) => f.description)
|
|
149
|
-
.filter(Boolean) as string[],
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
import { fetchWithRetry } from "./errors.js";
|
|
16
|
+
|
|
17
|
+
const GRANTS = "https://api.grants.gov/v1/api";
|
|
18
|
+
|
|
19
|
+
async function postJson<T>(
|
|
20
|
+
endpoint: string,
|
|
21
|
+
body: Record<string, unknown>,
|
|
22
|
+
): Promise<T> {
|
|
23
|
+
const r = await fetchWithRetry(
|
|
24
|
+
`${GRANTS}/${endpoint}`,
|
|
25
|
+
{
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: { "Content-Type": "application/json" },
|
|
28
|
+
body: JSON.stringify(body),
|
|
29
|
+
signal: AbortSignal.timeout(15_000),
|
|
30
|
+
},
|
|
31
|
+
`grants.gov:${endpoint}`,
|
|
32
|
+
);
|
|
33
|
+
return (await r.json()) as T;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type GrantStatus = "forecasted" | "posted" | "closed" | "archived";
|
|
37
|
+
|
|
38
|
+
export async function searchGrants(args: {
|
|
39
|
+
keyword?: string;
|
|
40
|
+
cfda?: string; // CFDA program number, e.g. "10.500"
|
|
41
|
+
agency?: string; // agency code, e.g. "DHS-FEMA"
|
|
42
|
+
oppNum?: string; // opportunity number
|
|
43
|
+
oppStatuses?: GrantStatus[];
|
|
44
|
+
rows?: number;
|
|
45
|
+
}) {
|
|
46
|
+
const body: Record<string, unknown> = {
|
|
47
|
+
rows: args.rows ?? 10,
|
|
48
|
+
keyword: args.keyword ?? "",
|
|
49
|
+
cfda: args.cfda ?? "",
|
|
50
|
+
agencies: args.agency ?? "",
|
|
51
|
+
oppNum: args.oppNum ?? "",
|
|
52
|
+
oppStatuses: (args.oppStatuses ?? ["forecasted", "posted"]).join("|"),
|
|
53
|
+
};
|
|
54
|
+
type Resp = {
|
|
55
|
+
errorcode?: number;
|
|
56
|
+
msg?: string;
|
|
57
|
+
data?: {
|
|
58
|
+
hitCount?: number;
|
|
59
|
+
oppHits?: {
|
|
60
|
+
id?: string;
|
|
61
|
+
number?: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
agencyCode?: string;
|
|
64
|
+
agencyName?: string;
|
|
65
|
+
openDate?: string;
|
|
66
|
+
closeDate?: string;
|
|
67
|
+
oppStatus?: string;
|
|
68
|
+
docType?: string;
|
|
69
|
+
cfdaList?: string;
|
|
70
|
+
}[];
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const json = await postJson<Resp>("search2", body);
|
|
74
|
+
if (json.errorcode && json.errorcode !== 0) {
|
|
75
|
+
throw new Error(`Grants.gov error: ${json.msg ?? "unknown"}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
totalRecords: json.data?.hitCount ?? 0,
|
|
79
|
+
grants: (json.data?.oppHits ?? []).map((g) => ({
|
|
80
|
+
id: g.id ?? "",
|
|
81
|
+
opportunityNumber: g.number ?? "",
|
|
82
|
+
title: g.title ?? "",
|
|
83
|
+
agencyCode: g.agencyCode ?? "",
|
|
84
|
+
agencyName: g.agencyName ?? "",
|
|
85
|
+
openDate: g.openDate,
|
|
86
|
+
closeDate: g.closeDate,
|
|
87
|
+
status: g.oppStatus,
|
|
88
|
+
docType: g.docType,
|
|
89
|
+
cfdaList: g.cfdaList,
|
|
90
|
+
})),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function getGrant(args: { opportunityId: string }) {
|
|
95
|
+
type Resp = {
|
|
96
|
+
errorcode?: number;
|
|
97
|
+
msg?: string;
|
|
98
|
+
data?: {
|
|
99
|
+
id?: number;
|
|
100
|
+
opportunityNumber?: string;
|
|
101
|
+
opportunityTitle?: string;
|
|
102
|
+
owningAgencyCode?: string;
|
|
103
|
+
synopsisDesc?: string;
|
|
104
|
+
synopsis?: {
|
|
105
|
+
synopsisDesc?: string;
|
|
106
|
+
applicantTypes?: { description?: string }[];
|
|
107
|
+
fundingActivityCategories?: { description?: string }[];
|
|
108
|
+
fundingInstruments?: { description?: string }[];
|
|
109
|
+
responseDate?: string;
|
|
110
|
+
postingDate?: string;
|
|
111
|
+
archiveDate?: string;
|
|
112
|
+
awardCeiling?: number;
|
|
113
|
+
awardFloor?: number;
|
|
114
|
+
estimatedFunding?: number;
|
|
115
|
+
expectedNumberOfAwards?: number;
|
|
116
|
+
agencyName?: string;
|
|
117
|
+
agencyCode?: string;
|
|
118
|
+
};
|
|
119
|
+
opportunityHistoryDetails?: { actionType?: string; actionDate?: string }[];
|
|
120
|
+
cfdas?: { cfdaNumber?: string; programTitle?: string }[];
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const json = await postJson<Resp>("fetchOpportunity", {
|
|
124
|
+
opportunityId: args.opportunityId,
|
|
125
|
+
});
|
|
126
|
+
if (json.errorcode && json.errorcode !== 0) {
|
|
127
|
+
throw new Error(`Grants.gov error: ${json.msg ?? "unknown"}`);
|
|
128
|
+
}
|
|
129
|
+
const d = json.data ?? {};
|
|
130
|
+
const s = d.synopsis ?? {};
|
|
131
|
+
return {
|
|
132
|
+
id: d.id ?? 0,
|
|
133
|
+
opportunityNumber: d.opportunityNumber ?? "",
|
|
134
|
+
title: d.opportunityTitle ?? "",
|
|
135
|
+
agency: { code: s.agencyCode ?? d.owningAgencyCode, name: s.agencyName },
|
|
136
|
+
description: s.synopsisDesc ?? d.synopsisDesc ?? "",
|
|
137
|
+
postingDate: s.postingDate,
|
|
138
|
+
responseDate: s.responseDate,
|
|
139
|
+
archiveDate: s.archiveDate,
|
|
140
|
+
awardCeiling: s.awardCeiling,
|
|
141
|
+
awardFloor: s.awardFloor,
|
|
142
|
+
estimatedFunding: s.estimatedFunding,
|
|
143
|
+
expectedNumberOfAwards: s.expectedNumberOfAwards,
|
|
144
|
+
applicantTypes: (s.applicantTypes ?? [])
|
|
145
|
+
.map((a) => a.description)
|
|
146
|
+
.filter(Boolean) as string[],
|
|
147
|
+
fundingInstruments: (s.fundingInstruments ?? [])
|
|
148
|
+
.map((f) => f.description)
|
|
149
|
+
.filter(Boolean) as string[],
|
|
150
|
+
fundingCategories: (s.fundingActivityCategories ?? [])
|
|
151
|
+
.map((f) => f.description)
|
|
152
|
+
.filter(Boolean) as string[],
|
|
153
|
+
cfdaPrograms: (d.cfdas ?? []).map((c) => ({
|
|
154
|
+
number: c.cfdaNumber ?? "",
|
|
155
|
+
title: c.programTitle ?? "",
|
|
156
|
+
})),
|
|
157
|
+
};
|
|
158
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -29,9 +29,10 @@ import * as usas from "./usaspending.js";
|
|
|
29
29
|
import * as fedreg from "./federal-register.js";
|
|
30
30
|
import * as ecfr from "./ecfr.js";
|
|
31
31
|
import * as grants from "./grants.js";
|
|
32
|
+
import { toToolError } from "./errors.js";
|
|
32
33
|
|
|
33
34
|
const SERVER_NAME = "mcp-sam-gov";
|
|
34
|
-
const SERVER_VERSION = "0.
|
|
35
|
+
const SERVER_VERSION = "0.3.0";
|
|
35
36
|
|
|
36
37
|
// ─── Tool input schemas (Zod) ────────────────────────────────────
|
|
37
38
|
|
|
@@ -548,16 +549,24 @@ async function main() {
|
|
|
548
549
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
549
550
|
const { name, arguments: args } = req.params;
|
|
550
551
|
try {
|
|
551
|
-
const
|
|
552
|
+
const data = await runTool(name, args ?? {}, sam);
|
|
553
|
+
// Structured success envelope. Calling agent can rely on
|
|
554
|
+
// `ok: true` to know the payload is in `data`.
|
|
555
|
+
const envelope = { ok: true as const, data };
|
|
552
556
|
return {
|
|
553
557
|
content: [
|
|
554
|
-
{ type: "text" as const, text: JSON.stringify(
|
|
558
|
+
{ type: "text" as const, text: JSON.stringify(envelope, null, 2) },
|
|
555
559
|
],
|
|
556
560
|
};
|
|
557
561
|
} catch (err) {
|
|
558
|
-
|
|
562
|
+
// Structured error envelope. The agent can read `error.kind`
|
|
563
|
+
// and `error.retryable` to decide what to do next.
|
|
564
|
+
const error = toToolError(err, name);
|
|
565
|
+
const envelope = { ok: false as const, error };
|
|
559
566
|
return {
|
|
560
|
-
content: [
|
|
567
|
+
content: [
|
|
568
|
+
{ type: "text" as const, text: JSON.stringify(envelope, null, 2) },
|
|
569
|
+
],
|
|
561
570
|
isError: true,
|
|
562
571
|
};
|
|
563
572
|
}
|