@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.
- package/LICENSE +21 -0
- package/README.ja.md +184 -0
- package/README.ko.md +184 -0
- package/README.md +397 -0
- package/dist/ecfr.d.ts +44 -0
- package/dist/ecfr.d.ts.map +1 -0
- package/dist/ecfr.js +86 -0
- package/dist/ecfr.js.map +1 -0
- package/dist/federal-register.d.ts +82 -0
- package/dist/federal-register.d.ts.map +1 -0
- package/dist/federal-register.js +117 -0
- package/dist/federal-register.js.map +1 -0
- package/dist/grants.d.ts +63 -0
- package/dist/grants.d.ts.map +1 -0
- package/dist/grants.js +93 -0
- package/dist/grants.js.map +1 -0
- package/dist/sam-gov/client.d.ts +69 -0
- package/dist/sam-gov/client.d.ts.map +1 -0
- package/dist/sam-gov/client.js +401 -0
- package/dist/sam-gov/client.js.map +1 -0
- package/dist/sam-gov/index.d.ts +19 -0
- package/dist/sam-gov/index.d.ts.map +1 -0
- package/dist/sam-gov/index.js +18 -0
- package/dist/sam-gov/index.js.map +1 -0
- package/dist/sam-gov/types.d.ts +109 -0
- package/dist/sam-gov/types.d.ts.map +1 -0
- package/dist/sam-gov/types.js +7 -0
- package/dist/sam-gov/types.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +685 -0
- package/dist/server.js.map +1 -0
- package/dist/usaspending.d.ts +369 -0
- package/dist/usaspending.d.ts.map +1 -0
- package/dist/usaspending.js +555 -0
- package/dist/usaspending.js.map +1 -0
- package/package.json +88 -0
- package/src/ecfr.ts +127 -0
- package/src/federal-register.ts +191 -0
- package/src/grants.ts +155 -0
- package/src/sam-gov/client.ts +492 -0
- package/src/sam-gov/index.ts +28 -0
- package/src/sam-gov/types.ts +130 -0
- package/src/server.ts +856 -0
- package/src/usaspending.ts +925 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @cliwant/mcp-sam-gov — Model Context Protocol server for SAM.gov
|
|
4
|
+
* + USAspending + Federal Register + eCFR + Grants.gov.
|
|
5
|
+
*
|
|
6
|
+
* 34 keyless tools wrapping every public federal-contracting data
|
|
7
|
+
* source that doesn't require an API key. Compatible with:
|
|
8
|
+
* - Claude Desktop (claude_desktop_config.json)
|
|
9
|
+
* - Claude Code (.mcp.json or `claude mcp add`)
|
|
10
|
+
* - Codex CLI (~/.codex/config.toml)
|
|
11
|
+
* - Cursor (Cursor settings → MCP)
|
|
12
|
+
* - Continue (continue config)
|
|
13
|
+
* - Gemini CLI (~/.gemini/settings.json)
|
|
14
|
+
*
|
|
15
|
+
* Transport: stdio JSON-RPC. Auth: zero (pass SAM_GOV_API_KEY env
|
|
16
|
+
* var to unlock higher SAM.gov rate limits + archives older than
|
|
17
|
+
* ~12 months — optional).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import {
|
|
23
|
+
CallToolRequestSchema,
|
|
24
|
+
ListToolsRequestSchema,
|
|
25
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { SamGovClient, type SamSetAside } from "./sam-gov/index.js";
|
|
28
|
+
import * as usas from "./usaspending.js";
|
|
29
|
+
import * as fedreg from "./federal-register.js";
|
|
30
|
+
import * as ecfr from "./ecfr.js";
|
|
31
|
+
import * as grants from "./grants.js";
|
|
32
|
+
|
|
33
|
+
const SERVER_NAME = "mcp-sam-gov";
|
|
34
|
+
const SERVER_VERSION = "0.2.1";
|
|
35
|
+
|
|
36
|
+
// ─── Tool input schemas (Zod) ────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const SamSearchInput = z.object({
|
|
39
|
+
query: z.string().optional().describe("Free-text title query"),
|
|
40
|
+
ncode: z.string().optional().describe("NAICS code, e.g. '541512'"),
|
|
41
|
+
organizationName: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe(
|
|
45
|
+
"Issuing agency canonical name (e.g. 'Department of Veterans Affairs'). Use sam_lookup_organization or usas_lookup_agency to resolve abbreviations.",
|
|
46
|
+
),
|
|
47
|
+
state: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Place-of-performance state, 2-letter, e.g. 'MD'"),
|
|
51
|
+
setAside: z.array(z.string()).optional().describe(
|
|
52
|
+
"Set-aside codes: SBA, 8A, HZS, SDVOSBC, WOSB, EDWOSB, VSA, VSS",
|
|
53
|
+
),
|
|
54
|
+
limit: z.number().min(1).max(50).optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const SamGetOpportunityInput = z.object({
|
|
58
|
+
noticeId: z.string().describe("32-char hex notice id"),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const SamFetchDescriptionInput = z.object({
|
|
62
|
+
noticeId: z.string().describe("32-char hex notice id"),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const SamAttachmentUrlInput = z.object({
|
|
66
|
+
resourceId: z
|
|
67
|
+
.string()
|
|
68
|
+
.describe(
|
|
69
|
+
"Resource id from sam_get_opportunity → resourceLinks (URL-tail hex)",
|
|
70
|
+
),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// USAspending — awards & recipients
|
|
74
|
+
const UsasFiltersBase = z.object({
|
|
75
|
+
agency: z.string().optional().describe("Canonical agency name"),
|
|
76
|
+
naics: z.string().optional(),
|
|
77
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
78
|
+
setAside: z
|
|
79
|
+
.enum(["SBA", "8A", "HZS", "SDVOSBC", "WOSB", "EDWOSB", "VSA", "VSS"])
|
|
80
|
+
.optional(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const UsasIndividualAwardsInput = UsasFiltersBase.extend({
|
|
84
|
+
limit: z.number().min(1).max(50).optional(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const UsasSubAgencyInput = z.object({
|
|
88
|
+
agency: z.string(),
|
|
89
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const UsasLookupAgencyInput = z.object({
|
|
93
|
+
searchText: z.string().describe("Agency name or abbreviation"),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const UsasRecipientAwardsInput = z.object({
|
|
97
|
+
recipientName: z.string(),
|
|
98
|
+
agency: z.string().optional(),
|
|
99
|
+
naics: z.string().optional(),
|
|
100
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
101
|
+
limit: z.number().min(1).max(50).optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const UsasSubawardsInput = z.object({
|
|
105
|
+
primeRecipientName: z.string().optional(),
|
|
106
|
+
agency: z.string().optional(),
|
|
107
|
+
naics: z.string().optional(),
|
|
108
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
109
|
+
limit: z.number().min(1).max(50).optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const UsasExpiringInput = z.object({
|
|
113
|
+
agency: z.string().optional(),
|
|
114
|
+
naics: z.string().optional(),
|
|
115
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
116
|
+
monthsUntilExpiry: z.number().min(1).max(36).optional(),
|
|
117
|
+
minAwardValue: z.number().optional(),
|
|
118
|
+
limit: z.number().min(1).max(20).optional(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const UsasAwardDetailInput = z.object({
|
|
122
|
+
generatedInternalId: z
|
|
123
|
+
.string()
|
|
124
|
+
.describe("From spending_by_award results — e.g. CONT_AWD_*"),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const UsasSpendingOverTimeInput = z.object({
|
|
128
|
+
group: z.enum(["fiscal_year", "quarter", "month"]).optional(),
|
|
129
|
+
agency: z.string().optional(),
|
|
130
|
+
naics: z.string().optional(),
|
|
131
|
+
setAside: z
|
|
132
|
+
.enum(["SBA", "8A", "HZS", "SDVOSBC", "WOSB", "EDWOSB", "VSA", "VSS"])
|
|
133
|
+
.optional(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const UsasCategorySpendingInput = z.object({
|
|
137
|
+
agency: z.string().optional(),
|
|
138
|
+
naics: z.string().optional(),
|
|
139
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
140
|
+
limit: z.number().min(1).max(50).optional(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const UsasCfdaInput = z.object({
|
|
144
|
+
agency: z.string().optional(),
|
|
145
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
146
|
+
limit: z.number().min(1).max(50).optional(),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const UsasAgencySpendingInput = z.object({
|
|
150
|
+
naics: z.string().optional(),
|
|
151
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
152
|
+
setAside: z
|
|
153
|
+
.enum(["SBA", "8A", "HZS", "SDVOSBC", "WOSB", "EDWOSB", "VSA", "VSS"])
|
|
154
|
+
.optional(),
|
|
155
|
+
limit: z.number().min(1).max(50).optional(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const UsasAgencyProfileInput = z.object({
|
|
159
|
+
toptierCode: z
|
|
160
|
+
.string()
|
|
161
|
+
.describe(
|
|
162
|
+
"3-4 digit toptier code from usas_lookup_agency (e.g. '036' for VA)",
|
|
163
|
+
),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const UsasAgencyAwardsInput = UsasAgencyProfileInput.extend({
|
|
167
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const UsasAgencyBudgetInput = UsasAgencyProfileInput.extend({
|
|
171
|
+
fiscalYear: z.number().int().min(2007).optional(),
|
|
172
|
+
limit: z.number().min(1).max(20).optional(),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const UsasSearchRecipientsInput = z.object({
|
|
176
|
+
keyword: z.string(),
|
|
177
|
+
recipientLevel: z
|
|
178
|
+
.enum(["P", "C", "R"])
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("P=parent, C=child, R=recipient"),
|
|
181
|
+
limit: z.number().min(1).max(50).optional(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const UsasGetRecipientInput = z.object({
|
|
185
|
+
recipientId: z.string().describe("From usas_search_recipients — e.g. 'ed02855e-...-P'"),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const UsasAutocompleteInput = z.object({
|
|
189
|
+
searchText: z.string(),
|
|
190
|
+
limit: z.number().min(1).max(20).optional(),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const UsasNaicsHierarchyInput = z.object({
|
|
194
|
+
naicsFilter: z
|
|
195
|
+
.string()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("Filter to a specific NAICS code subtree, e.g. '541512'"),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const UsasGlossaryInput = z.object({
|
|
201
|
+
search: z.string().optional(),
|
|
202
|
+
limit: z.number().min(1).max(50).optional(),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const UsasListAgenciesInput = z.object({
|
|
206
|
+
limit: z.number().min(1).max(150).optional(),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Federal Register
|
|
210
|
+
const FedRegSearchInput = z.object({
|
|
211
|
+
query: z.string().optional(),
|
|
212
|
+
agencySlugs: z
|
|
213
|
+
.array(z.string())
|
|
214
|
+
.optional()
|
|
215
|
+
.describe(
|
|
216
|
+
"Federal Register agency slugs, e.g. ['veterans-affairs-department']. Use fed_register_list_agencies to resolve.",
|
|
217
|
+
),
|
|
218
|
+
type: z
|
|
219
|
+
.enum(["RULE", "PRORULE", "NOTICE", "PRESDOCU"])
|
|
220
|
+
.optional()
|
|
221
|
+
.describe("Document type"),
|
|
222
|
+
publicationDateFrom: z.string().optional().describe("YYYY-MM-DD"),
|
|
223
|
+
publicationDateTo: z.string().optional().describe("YYYY-MM-DD"),
|
|
224
|
+
effectiveDateFrom: z.string().optional().describe("YYYY-MM-DD"),
|
|
225
|
+
perPage: z.number().min(1).max(100).optional(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const FedRegGetDocInput = z.object({
|
|
229
|
+
documentNumber: z
|
|
230
|
+
.string()
|
|
231
|
+
.describe("Federal Register document number, e.g. '2026-08333'"),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const FedRegListAgenciesInput = z.object({
|
|
235
|
+
perPage: z.number().min(1).max(500).optional(),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// eCFR
|
|
239
|
+
const EcfrSearchInput = z.object({
|
|
240
|
+
query: z.string(),
|
|
241
|
+
titleNumber: z
|
|
242
|
+
.number()
|
|
243
|
+
.optional()
|
|
244
|
+
.describe(
|
|
245
|
+
"CFR title (1-50). e.g. 48 = FAR (Federal Acquisition Regulation), 2 = Federal financial assistance.",
|
|
246
|
+
),
|
|
247
|
+
perPage: z.number().min(1).max(20).optional(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const EcfrListTitlesInput = z.object({});
|
|
251
|
+
|
|
252
|
+
// Grants.gov
|
|
253
|
+
const GrantsSearchInput = z.object({
|
|
254
|
+
keyword: z.string().optional(),
|
|
255
|
+
cfda: z.string().optional().describe("CFDA program number, e.g. '10.500'"),
|
|
256
|
+
agency: z
|
|
257
|
+
.string()
|
|
258
|
+
.optional()
|
|
259
|
+
.describe("Grants.gov agency code, e.g. 'DHS-FEMA'"),
|
|
260
|
+
oppNum: z.string().optional().describe("Specific opportunity number"),
|
|
261
|
+
oppStatuses: z
|
|
262
|
+
.array(z.enum(["forecasted", "posted", "closed", "archived"]))
|
|
263
|
+
.optional()
|
|
264
|
+
.describe("Defaults to forecasted+posted"),
|
|
265
|
+
rows: z.number().min(1).max(50).optional(),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const GrantsGetInput = z.object({
|
|
269
|
+
opportunityId: z.string().describe("Grants.gov opportunity id (numeric string)"),
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// SAM.gov organization lookup (federal hierarchy)
|
|
273
|
+
const SamLookupOrgInput = z.object({
|
|
274
|
+
organizationId: z.string().describe("SAM.gov federal-organization id (numeric)"),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ─── Tool catalog ────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
type ToolDef = {
|
|
280
|
+
name: string;
|
|
281
|
+
description: string;
|
|
282
|
+
inputSchema: z.ZodTypeAny;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const TOOLS: ToolDef[] = [
|
|
286
|
+
// ━━━ SAM.gov (5) ━━━
|
|
287
|
+
{
|
|
288
|
+
name: "sam_search_opportunities",
|
|
289
|
+
description:
|
|
290
|
+
"Search SAM.gov federal contracting opportunities (keyless HAL). Returns up to 50 active notices with title, agency, NAICS, noticeId. Use for discovery — narrow with NAICS / agency / set-aside / state.",
|
|
291
|
+
inputSchema: SamSearchInput,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "sam_get_opportunity",
|
|
295
|
+
description:
|
|
296
|
+
"Fetch full detail for a single SAM.gov notice by 32-char hex noticeId. Returns title, agency, solicitation #, POCs, response deadline, attachments (with download URLs), inline description body. Call BEFORE drafting bid/no-bid or compliance work.",
|
|
297
|
+
inputSchema: SamGetOpportunityInput,
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "sam_fetch_description",
|
|
301
|
+
description:
|
|
302
|
+
"Return the full description / RFP body text for a notice as plain text. Useful when sam_get_opportunity returned a description URL instead of inline body, or for an LLM-friendly text dump.",
|
|
303
|
+
inputSchema: SamFetchDescriptionInput,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "sam_attachment_url",
|
|
307
|
+
description:
|
|
308
|
+
"Build the public download URL for an attachment resourceId. The URL returns a 303 redirect to a signed S3 URL — fetch with redirect:'follow' to get the file bytes.",
|
|
309
|
+
inputSchema: SamAttachmentUrlInput,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "sam_lookup_organization",
|
|
313
|
+
description:
|
|
314
|
+
"Resolve a SAM.gov federal-organization id to its canonical fullParentPathName (e.g. 'VETERANS AFFAIRS, DEPARTMENT OF.VETERANS AFFAIRS, DEPARTMENT OF.245-NETWORK CONTRACT OFFICE 5'). Use when sam_get_opportunity returned only an organizationId.",
|
|
315
|
+
inputSchema: SamLookupOrgInput,
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// ━━━ USAspending — Awards & Recipients (8) ━━━
|
|
319
|
+
{
|
|
320
|
+
name: "usas_search_awards",
|
|
321
|
+
description:
|
|
322
|
+
"Aggregate share-of-wallet on USAspending. Given an agency × NAICS × fiscal year, returns top recipients by total $ + count. Use for competitive landscape ('who wins at VA in 541512?').",
|
|
323
|
+
inputSchema: UsasFiltersBase,
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "usas_search_individual_awards",
|
|
327
|
+
description:
|
|
328
|
+
"Line-item federal contracts on USAspending. Returns specific awards (recipient + $ + sub-agency + state + description). Use AFTER usas_search_awards when the user wants 'show me the actual contracts'. Each result includes a generatedInternalId for usas_get_award_detail follow-ups.",
|
|
329
|
+
inputSchema: UsasIndividualAwardsInput,
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "usas_search_subagency_spending",
|
|
333
|
+
description:
|
|
334
|
+
"Break down a parent agency's spending by sub-agency / office. Surfaces which office holds the budget (e.g. VA OI&T vs VHA, DoD vs Army vs DISA).",
|
|
335
|
+
inputSchema: UsasSubAgencyInput,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "usas_lookup_agency",
|
|
339
|
+
description:
|
|
340
|
+
"Resolve a user-friendly agency reference ('VA', 'Veterans Affairs', 'DHS') to USAspending's canonical toptier name + 4-digit code. ALWAYS call this FIRST if the user uses an abbreviation — other USAspending tools require the canonical name.",
|
|
341
|
+
inputSchema: UsasLookupAgencyInput,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: "usas_search_awards_by_recipient",
|
|
345
|
+
description:
|
|
346
|
+
"Pull every contract a specific recipient has won within an agency × NAICS slice. Use when the user asks 'show me Booz Allen wins at VA last year' — returns line items + naicsCode + description, not aggregates.",
|
|
347
|
+
inputSchema: UsasRecipientAwardsInput,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "usas_search_subawards",
|
|
351
|
+
description:
|
|
352
|
+
"Enumerate subcontracts on prime awards. Use for 'who teams with Leidos at DISA' or 'show small-business subs on Accenture's DHS contracts' — surfaces the prime/sub network for teaming-map artifacts.",
|
|
353
|
+
inputSchema: UsasSubawardsInput,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "usas_search_expiring_contracts",
|
|
357
|
+
description:
|
|
358
|
+
"Find federal contracts at agency × NAICS that expire within N months. Recompete radar — end-date sorted, top 10 by value. Use for 'what VA cloud contracts are up for recompete' or 'show 541512 contracts expiring in 6 months'.",
|
|
359
|
+
inputSchema: UsasExpiringInput,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "usas_get_award_detail",
|
|
363
|
+
description:
|
|
364
|
+
"Fetch full detail for a single award by generatedInternalId (from usas_search_individual_awards). Returns period_of_performance (start/end/potential_end), base_and_all_options, set-aside type, competition extent, number_of_offers — the per-award fields the search endpoint omits.",
|
|
365
|
+
inputSchema: UsasAwardDetailInput,
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// ━━━ USAspending — Aggregate Analysis (6) ━━━
|
|
369
|
+
{
|
|
370
|
+
name: "usas_spending_over_time",
|
|
371
|
+
description:
|
|
372
|
+
"Time-series aggregation of federal spending. Group by fiscal_year / quarter / month, filter by agency / NAICS / set-aside. Use for 'how has VA 541512 spending trended over the past 5 years' — returns yearly/quarterly/monthly $ rollups.",
|
|
373
|
+
inputSchema: UsasSpendingOverTimeInput,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "usas_search_psc_spending",
|
|
377
|
+
description:
|
|
378
|
+
"Spending broken down by Product Service Code (PSC). Use for 'what PSC categories see the most $ at DoD' — surfaces market structure beyond NAICS (e.g. PSC R425 = engineering support services).",
|
|
379
|
+
inputSchema: UsasCategorySpendingInput,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: "usas_search_state_spending",
|
|
383
|
+
description:
|
|
384
|
+
"Spending broken down by state / territory. Use for 'where is the most federal $ flowing for NAICS 541512' — answers like 'VA $128B, MD $66B, DC $58B'.",
|
|
385
|
+
inputSchema: UsasCategorySpendingInput,
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "usas_search_cfda_spending",
|
|
389
|
+
description:
|
|
390
|
+
"Spending broken down by CFDA grant program code. Use for grant analysis — 'top federal grant programs by $'. Note: CFDA is grants (award_type 02-05), not contracts. Use usas_search_psc_spending for contract market analysis.",
|
|
391
|
+
inputSchema: UsasCfdaInput,
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "usas_search_federal_account_spending",
|
|
395
|
+
description:
|
|
396
|
+
"Spending broken down by federal account / Treasury Account Symbol (TAS). Use to map money to the actual budget line item (e.g. '036-0167 = Information Technology Systems, VA').",
|
|
397
|
+
inputSchema: UsasCategorySpendingInput,
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: "usas_search_agency_spending",
|
|
401
|
+
description:
|
|
402
|
+
"Spending broken down by awarding agency. Use for 'which agencies spend the most on NAICS 541512' — top buyers by $.",
|
|
403
|
+
inputSchema: UsasAgencySpendingInput,
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
// ━━━ USAspending — Agency Profile (3) ━━━
|
|
407
|
+
{
|
|
408
|
+
name: "usas_get_agency_profile",
|
|
409
|
+
description:
|
|
410
|
+
"Get full agency profile by toptier code (3-4 digits, from usas_lookup_agency). Returns mission, abbreviation, website, subtier_agency_count, congressional_justification_url.",
|
|
411
|
+
inputSchema: UsasAgencyProfileInput,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "usas_get_agency_awards_summary",
|
|
415
|
+
description:
|
|
416
|
+
"High-level award activity for a fiscal year — transaction_count + obligations + latest_action_date. Snapshot of agency volume.",
|
|
417
|
+
inputSchema: UsasAgencyAwardsInput,
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: "usas_get_agency_budget_function",
|
|
421
|
+
description:
|
|
422
|
+
"Budget function breakdown for an agency × fiscal year. Returns the agency's spending by program area (e.g. VA: 'Income security for veterans' $204B, 'Hospital and medical care for veterans' $126B).",
|
|
423
|
+
inputSchema: UsasAgencyBudgetInput,
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// ━━━ USAspending — Recipient Profile (2) ━━━
|
|
427
|
+
{
|
|
428
|
+
name: "usas_search_recipients",
|
|
429
|
+
description:
|
|
430
|
+
"Search USAspending recipient list with parent/child/recipient hierarchy. Returns recipients with id, duns, uei, level (P=parent, C=child, R=recipient), total_amount. Use for 'find the recipient_id for Booz Allen' before usas_get_recipient_profile.",
|
|
431
|
+
inputSchema: UsasSearchRecipientsInput,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
name: "usas_get_recipient_profile",
|
|
435
|
+
description:
|
|
436
|
+
"Full recipient detail by recipient_id (from usas_search_recipients). Returns alternate_names (M&A history), DUNS, UEI, parent linkage, business_types, location, total_amount, total_transactions.",
|
|
437
|
+
inputSchema: UsasGetRecipientInput,
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
// ━━━ USAspending — Reference / Autocomplete (4) ━━━
|
|
441
|
+
{
|
|
442
|
+
name: "usas_autocomplete_naics",
|
|
443
|
+
description:
|
|
444
|
+
"Autocomplete NAICS codes by free-text. ANTI-HALLUCINATION GUARD — call this when the user mentions a NAICS theme but no specific code (e.g. 'computer systems design' → 541512). Avoids inventing NAICS codes.",
|
|
445
|
+
inputSchema: UsasAutocompleteInput,
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "usas_autocomplete_recipient",
|
|
449
|
+
description:
|
|
450
|
+
"Autocomplete recipient names. ANTI-HALLUCINATION — confirm a recipient's exact USAspending-canonical legal name before searching by name. Returns up to 10 fuzzy matches with UEI/DUNS where available.",
|
|
451
|
+
inputSchema: UsasAutocompleteInput,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
name: "usas_naics_hierarchy",
|
|
455
|
+
description:
|
|
456
|
+
"Navigate the NAICS hierarchy (2-digit → 4-digit → 6-digit). Returns parent/child relationships + active-contract count per code. Use to explore market scope ('what's under NAICS 541' = 'Professional, Scientific, and Technical Services').",
|
|
457
|
+
inputSchema: UsasNaicsHierarchyInput,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "usas_glossary",
|
|
461
|
+
description:
|
|
462
|
+
"USAspending glossary of 151 federal-spending terms. Use to confirm terminology ('what's a TAS?', 'what's an obligation vs outlay?') before answering compliance/budget questions.",
|
|
463
|
+
inputSchema: UsasGlossaryInput,
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "usas_list_toptier_agencies",
|
|
467
|
+
description:
|
|
468
|
+
"List all toptier federal agencies with toptier_code, abbreviation, slug, current-FY obligations. Use for 'show me every cabinet department + their FY26 spending' or to find a toptier_code for usas_get_agency_*.",
|
|
469
|
+
inputSchema: UsasListAgenciesInput,
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// ━━━ Federal Register (3) ━━━
|
|
473
|
+
{
|
|
474
|
+
name: "fed_register_search_documents",
|
|
475
|
+
description:
|
|
476
|
+
"Search Federal Register documents (proposed rules, final rules, notices, presidential documents) by query / agency / type / date range. Use for regulatory-context queries ('what new VA cybersecurity rules came out this quarter?').",
|
|
477
|
+
inputSchema: FedRegSearchInput,
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "fed_register_get_document",
|
|
481
|
+
description:
|
|
482
|
+
"Fetch full detail for a Federal Register document by number. Returns title, abstract, citation, publication_date, effective_on, raw_text_url (for the full body), CFR references — everything needed to ground a regulation citation.",
|
|
483
|
+
inputSchema: FedRegGetDocInput,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "fed_register_list_agencies",
|
|
487
|
+
description:
|
|
488
|
+
"List all Federal Register agencies with slugs (needed for fed_register_search_documents). Use to resolve 'what's the FedReg slug for Veterans Affairs?'",
|
|
489
|
+
inputSchema: FedRegListAgenciesInput,
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
// ━━━ eCFR (2) ━━━
|
|
493
|
+
{
|
|
494
|
+
name: "ecfr_search",
|
|
495
|
+
description:
|
|
496
|
+
"Full-text search across the entire CFR (Code of Federal Regulations). Use for compliance questions — pass titleNumber=48 for FAR (Federal Acquisition Regulation), titleNumber=2 for federal financial assistance, etc. Returns excerpt + section path + ecfrUrl.",
|
|
497
|
+
inputSchema: EcfrSearchInput,
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "ecfr_list_titles",
|
|
501
|
+
description:
|
|
502
|
+
"List all 50 CFR titles with name + last_amended_on date. Use to discover what's in each title (Title 48 = FAR, Title 32 = National Defense, Title 14 = Aeronautics, etc.).",
|
|
503
|
+
inputSchema: EcfrListTitlesInput,
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
// ━━━ Grants.gov (2) ━━━
|
|
507
|
+
{
|
|
508
|
+
name: "grants_search",
|
|
509
|
+
description:
|
|
510
|
+
"Search Grants.gov federal grant opportunities (financial assistance, distinct from contracts on SAM.gov). Filter by keyword / CFDA / agency / opportunity number. Default status = forecasted + posted.",
|
|
511
|
+
inputSchema: GrantsSearchInput,
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "grants_get_opportunity",
|
|
515
|
+
description:
|
|
516
|
+
"Fetch full detail for a single grant opportunity by id. Returns description, agency, posting/response/archive dates, award_ceiling, award_floor, estimated_funding, expected_number_of_awards, applicant_types, funding_instruments, CFDA programs.",
|
|
517
|
+
inputSchema: GrantsGetInput,
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
// ─── Server bootstrap ────────────────────────────────────────────
|
|
522
|
+
|
|
523
|
+
async function main() {
|
|
524
|
+
const sam = new SamGovClient({
|
|
525
|
+
apiKey: process.env.SAM_GOV_API_KEY?.trim() || undefined,
|
|
526
|
+
logger: {
|
|
527
|
+
warn: (msg, err) => {
|
|
528
|
+
console.error(msg, err ?? "");
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const server = new Server(
|
|
534
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
535
|
+
{ capabilities: { tools: {} } },
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
539
|
+
return {
|
|
540
|
+
tools: TOOLS.map((t) => ({
|
|
541
|
+
name: t.name,
|
|
542
|
+
description: t.description,
|
|
543
|
+
inputSchema: zodToJsonSchema(t.inputSchema),
|
|
544
|
+
})),
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
549
|
+
const { name, arguments: args } = req.params;
|
|
550
|
+
try {
|
|
551
|
+
const result = await runTool(name, args ?? {}, sam);
|
|
552
|
+
return {
|
|
553
|
+
content: [
|
|
554
|
+
{ type: "text" as const, text: JSON.stringify(result, null, 2) },
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
} catch (err) {
|
|
558
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: "text" as const, text: `Tool error: ${message}` }],
|
|
561
|
+
isError: true,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const transport = new StdioServerTransport();
|
|
567
|
+
await server.connect(transport);
|
|
568
|
+
console.error(
|
|
569
|
+
`[mcp-sam-gov] v${SERVER_VERSION} listening on stdio (${TOOLS.length} tools).`,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function runTool(
|
|
574
|
+
name: string,
|
|
575
|
+
args: Record<string, unknown>,
|
|
576
|
+
sam: SamGovClient,
|
|
577
|
+
): Promise<unknown> {
|
|
578
|
+
switch (name) {
|
|
579
|
+
// SAM.gov
|
|
580
|
+
case "sam_search_opportunities": {
|
|
581
|
+
const input = SamSearchInput.parse(args);
|
|
582
|
+
const r = await sam.searchOpportunities({
|
|
583
|
+
...input,
|
|
584
|
+
setAside: input.setAside as SamSetAside[] | undefined,
|
|
585
|
+
});
|
|
586
|
+
return {
|
|
587
|
+
totalRecords: r.totalRecords,
|
|
588
|
+
returned: r.opportunitiesData.length,
|
|
589
|
+
opportunities: r.opportunitiesData.map((o) => ({
|
|
590
|
+
noticeId: o.noticeId,
|
|
591
|
+
title: o.title,
|
|
592
|
+
agency: o.fullParentPathName,
|
|
593
|
+
solicitationNumber: o.solicitationNumber,
|
|
594
|
+
responseDeadline: o.responseDeadLine,
|
|
595
|
+
naics: o.naicsCode,
|
|
596
|
+
setAside: o.typeOfSetAside,
|
|
597
|
+
uiLink: o.uiLink,
|
|
598
|
+
})),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
case "sam_get_opportunity": {
|
|
602
|
+
const { noticeId } = SamGetOpportunityInput.parse(args);
|
|
603
|
+
const o = await sam.getOpportunity(noticeId);
|
|
604
|
+
if (!o) return { found: false, noticeId };
|
|
605
|
+
return {
|
|
606
|
+
found: true,
|
|
607
|
+
noticeId: o.noticeId,
|
|
608
|
+
title: o.title,
|
|
609
|
+
agency: o.fullParentPathName,
|
|
610
|
+
solicitationNumber: o.solicitationNumber,
|
|
611
|
+
responseDeadline: o.responseDeadLine,
|
|
612
|
+
type: o.type,
|
|
613
|
+
naics: o.naicsCode,
|
|
614
|
+
setAside: o.typeOfSetAside,
|
|
615
|
+
placeOfPerformance: o.placeOfPerformance,
|
|
616
|
+
pointsOfContact: o.pointOfContact ?? [],
|
|
617
|
+
description: o.description,
|
|
618
|
+
attachments: (o.resourceLinks ?? []).map((url, idx) => ({
|
|
619
|
+
index: idx,
|
|
620
|
+
url,
|
|
621
|
+
})),
|
|
622
|
+
uiLink: o.uiLink,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
case "sam_fetch_description": {
|
|
626
|
+
const { noticeId } = SamFetchDescriptionInput.parse(args);
|
|
627
|
+
const o = await sam.getOpportunity(noticeId);
|
|
628
|
+
if (!o) return { found: false, noticeId };
|
|
629
|
+
const text = o.description
|
|
630
|
+
? await sam.fetchOpportunityDescription(o.description)
|
|
631
|
+
: "";
|
|
632
|
+
return {
|
|
633
|
+
found: true,
|
|
634
|
+
noticeId,
|
|
635
|
+
descriptionLength: text.length,
|
|
636
|
+
description: text || "(no description body available)",
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
case "sam_attachment_url": {
|
|
640
|
+
const { resourceId } = SamAttachmentUrlInput.parse(args);
|
|
641
|
+
return { downloadUrl: sam.publicDownloadUrl(resourceId) };
|
|
642
|
+
}
|
|
643
|
+
case "sam_lookup_organization": {
|
|
644
|
+
const { organizationId } = SamLookupOrgInput.parse(args);
|
|
645
|
+
// SamGovClient internal method — exposed via direct fetch since
|
|
646
|
+
// it's not on the public surface. Use the public sam.gov endpoint
|
|
647
|
+
// directly (already keyless).
|
|
648
|
+
const r = await fetch(
|
|
649
|
+
`https://sam.gov/api/prod/federalorganizations/v1/organizations/${encodeURIComponent(organizationId)}`,
|
|
650
|
+
{
|
|
651
|
+
headers: { Accept: "application/hal+json" },
|
|
652
|
+
signal: AbortSignal.timeout(10_000),
|
|
653
|
+
},
|
|
654
|
+
);
|
|
655
|
+
if (!r.ok) {
|
|
656
|
+
return { found: false, organizationId, status: r.status };
|
|
657
|
+
}
|
|
658
|
+
type Resp = {
|
|
659
|
+
_embedded?: {
|
|
660
|
+
org?: {
|
|
661
|
+
fullParentPathName?: string;
|
|
662
|
+
agencyName?: string;
|
|
663
|
+
name?: string;
|
|
664
|
+
type?: string;
|
|
665
|
+
level?: number;
|
|
666
|
+
};
|
|
667
|
+
}[];
|
|
668
|
+
};
|
|
669
|
+
const json = (await r.json()) as Resp;
|
|
670
|
+
const org = json._embedded?.[0]?.org;
|
|
671
|
+
return {
|
|
672
|
+
found: !!org,
|
|
673
|
+
organizationId,
|
|
674
|
+
fullParentPathName: org?.fullParentPathName ?? "",
|
|
675
|
+
agencyName: org?.agencyName ?? "",
|
|
676
|
+
name: org?.name ?? "",
|
|
677
|
+
type: org?.type,
|
|
678
|
+
level: org?.level,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// USAspending — Awards & Recipients
|
|
683
|
+
case "usas_search_awards":
|
|
684
|
+
return await usas.searchAwards(UsasFiltersBase.parse(args));
|
|
685
|
+
case "usas_search_individual_awards":
|
|
686
|
+
return await usas.searchIndividualAwards(
|
|
687
|
+
UsasIndividualAwardsInput.parse(args),
|
|
688
|
+
);
|
|
689
|
+
case "usas_search_subagency_spending":
|
|
690
|
+
return await usas.searchSubAgencySpending(UsasSubAgencyInput.parse(args));
|
|
691
|
+
case "usas_lookup_agency":
|
|
692
|
+
return await usas.lookupAgency(
|
|
693
|
+
UsasLookupAgencyInput.parse(args).searchText,
|
|
694
|
+
);
|
|
695
|
+
case "usas_search_awards_by_recipient":
|
|
696
|
+
return await usas.searchAwardsByRecipient(
|
|
697
|
+
UsasRecipientAwardsInput.parse(args),
|
|
698
|
+
);
|
|
699
|
+
case "usas_search_subawards":
|
|
700
|
+
return await usas.searchSubawards(UsasSubawardsInput.parse(args));
|
|
701
|
+
case "usas_search_expiring_contracts":
|
|
702
|
+
return await usas.searchExpiringContracts(UsasExpiringInput.parse(args));
|
|
703
|
+
case "usas_get_award_detail":
|
|
704
|
+
return await usas.getAwardDetail(
|
|
705
|
+
UsasAwardDetailInput.parse(args).generatedInternalId,
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
// USAspending — Aggregate
|
|
709
|
+
case "usas_spending_over_time":
|
|
710
|
+
return await usas.spendingOverTime(
|
|
711
|
+
UsasSpendingOverTimeInput.parse(args),
|
|
712
|
+
);
|
|
713
|
+
case "usas_search_psc_spending":
|
|
714
|
+
return await usas.searchPscSpending(
|
|
715
|
+
UsasCategorySpendingInput.parse(args),
|
|
716
|
+
);
|
|
717
|
+
case "usas_search_state_spending":
|
|
718
|
+
return await usas.searchStateSpending(
|
|
719
|
+
UsasCategorySpendingInput.parse(args),
|
|
720
|
+
);
|
|
721
|
+
case "usas_search_cfda_spending":
|
|
722
|
+
return await usas.searchCfdaSpending(UsasCfdaInput.parse(args));
|
|
723
|
+
case "usas_search_federal_account_spending":
|
|
724
|
+
return await usas.searchFederalAccountSpending(
|
|
725
|
+
UsasCategorySpendingInput.parse(args),
|
|
726
|
+
);
|
|
727
|
+
case "usas_search_agency_spending":
|
|
728
|
+
return await usas.searchAgencySpending(
|
|
729
|
+
UsasAgencySpendingInput.parse(args),
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
// USAspending — Agency Profile
|
|
733
|
+
case "usas_get_agency_profile":
|
|
734
|
+
return await usas.getAgencyProfile(
|
|
735
|
+
UsasAgencyProfileInput.parse(args).toptierCode,
|
|
736
|
+
);
|
|
737
|
+
case "usas_get_agency_awards_summary":
|
|
738
|
+
return await usas.getAgencyAwardsSummary(
|
|
739
|
+
UsasAgencyAwardsInput.parse(args),
|
|
740
|
+
);
|
|
741
|
+
case "usas_get_agency_budget_function":
|
|
742
|
+
return await usas.getAgencyBudgetFunction(
|
|
743
|
+
UsasAgencyBudgetInput.parse(args),
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
// USAspending — Recipient Profile
|
|
747
|
+
case "usas_search_recipients":
|
|
748
|
+
return await usas.searchRecipients(UsasSearchRecipientsInput.parse(args));
|
|
749
|
+
case "usas_get_recipient_profile":
|
|
750
|
+
return await usas.getRecipientProfile(
|
|
751
|
+
UsasGetRecipientInput.parse(args).recipientId,
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
// USAspending — Reference / Autocomplete
|
|
755
|
+
case "usas_autocomplete_naics":
|
|
756
|
+
return await usas.autocompleteNaics(UsasAutocompleteInput.parse(args));
|
|
757
|
+
case "usas_autocomplete_recipient":
|
|
758
|
+
return await usas.autocompleteRecipient(
|
|
759
|
+
UsasAutocompleteInput.parse(args),
|
|
760
|
+
);
|
|
761
|
+
case "usas_naics_hierarchy":
|
|
762
|
+
return await usas.naicsHierarchy(UsasNaicsHierarchyInput.parse(args));
|
|
763
|
+
case "usas_glossary":
|
|
764
|
+
return await usas.glossary(UsasGlossaryInput.parse(args));
|
|
765
|
+
case "usas_list_toptier_agencies":
|
|
766
|
+
return await usas.listToptierAgencies(UsasListAgenciesInput.parse(args));
|
|
767
|
+
|
|
768
|
+
// Federal Register
|
|
769
|
+
case "fed_register_search_documents":
|
|
770
|
+
return await fedreg.searchDocuments(FedRegSearchInput.parse(args));
|
|
771
|
+
case "fed_register_get_document":
|
|
772
|
+
return await fedreg.getDocument(
|
|
773
|
+
FedRegGetDocInput.parse(args).documentNumber,
|
|
774
|
+
);
|
|
775
|
+
case "fed_register_list_agencies":
|
|
776
|
+
return await fedreg.listAgencies(FedRegListAgenciesInput.parse(args));
|
|
777
|
+
|
|
778
|
+
// eCFR
|
|
779
|
+
case "ecfr_search":
|
|
780
|
+
return await ecfr.search(EcfrSearchInput.parse(args));
|
|
781
|
+
case "ecfr_list_titles":
|
|
782
|
+
return await ecfr.listTitles();
|
|
783
|
+
|
|
784
|
+
// Grants.gov
|
|
785
|
+
case "grants_search":
|
|
786
|
+
return await grants.searchGrants(GrantsSearchInput.parse(args));
|
|
787
|
+
case "grants_get_opportunity":
|
|
788
|
+
return await grants.getGrant(GrantsGetInput.parse(args));
|
|
789
|
+
|
|
790
|
+
default:
|
|
791
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Hand-rolled Zod → JSON Schema converter (subset we use).
|
|
797
|
+
*/
|
|
798
|
+
function zodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown> {
|
|
799
|
+
const def = (schema as unknown as { _def: { typeName: string } })._def;
|
|
800
|
+
const tn = def.typeName;
|
|
801
|
+
const description = (schema as unknown as { description?: string }).description;
|
|
802
|
+
|
|
803
|
+
if (tn === "ZodObject") {
|
|
804
|
+
const shape = (schema as unknown as { shape: Record<string, z.ZodTypeAny> })
|
|
805
|
+
.shape;
|
|
806
|
+
const properties: Record<string, unknown> = {};
|
|
807
|
+
const required: string[] = [];
|
|
808
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
809
|
+
properties[key] = zodToJsonSchema(value);
|
|
810
|
+
if (
|
|
811
|
+
!(value as unknown as { isOptional: () => boolean }).isOptional?.()
|
|
812
|
+
) {
|
|
813
|
+
required.push(key);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return {
|
|
817
|
+
type: "object",
|
|
818
|
+
properties,
|
|
819
|
+
...(required.length > 0 ? { required } : {}),
|
|
820
|
+
...(description ? { description } : {}),
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
if (tn === "ZodString") return { type: "string", ...(description ? { description } : {}) };
|
|
824
|
+
if (tn === "ZodNumber") return { type: "number", ...(description ? { description } : {}) };
|
|
825
|
+
if (tn === "ZodBoolean") return { type: "boolean", ...(description ? { description } : {}) };
|
|
826
|
+
if (tn === "ZodArray") {
|
|
827
|
+
const inner = (schema as unknown as { _def: { type: z.ZodTypeAny } })._def
|
|
828
|
+
.type;
|
|
829
|
+
return {
|
|
830
|
+
type: "array",
|
|
831
|
+
items: zodToJsonSchema(inner),
|
|
832
|
+
...(description ? { description } : {}),
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
if (tn === "ZodEnum") {
|
|
836
|
+
const values = (schema as unknown as { _def: { values: string[] } })._def
|
|
837
|
+
.values;
|
|
838
|
+
return {
|
|
839
|
+
type: "string",
|
|
840
|
+
enum: values,
|
|
841
|
+
...(description ? { description } : {}),
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
if (tn === "ZodOptional" || tn === "ZodDefault" || tn === "ZodNullable") {
|
|
845
|
+
const inner = (schema as unknown as { _def: { innerType: z.ZodTypeAny } })
|
|
846
|
+
._def.innerType;
|
|
847
|
+
const innerSchema = zodToJsonSchema(inner);
|
|
848
|
+
return description ? { ...innerSchema, description } : innerSchema;
|
|
849
|
+
}
|
|
850
|
+
return { type: "string", ...(description ? { description } : {}) };
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
main().catch((err) => {
|
|
854
|
+
console.error("[mcp-sam-gov] FATAL:", err);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
});
|