freshcontext-mcp 0.3.3 → 0.3.5
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/USAGE.md +294 -0
- package/dist/adapters/govcontracts.js +91 -90
- package/package.json +1 -1
package/USAGE.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# FreshContext — Usage Guide
|
|
2
|
+
> *13 tools. No API keys. Every result timestamped.*
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Quick Start
|
|
7
|
+
|
|
8
|
+
Add FreshContext to Claude Desktop (no install required):
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"freshcontext": {
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": ["-y", "mcp-remote", "https://freshcontext-mcp.gimmanuel73.workers.dev/mcp"]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Restart Claude. Done. All 13 tools are now available.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The 13 Tools
|
|
26
|
+
|
|
27
|
+
| Tool | What it does | Input |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `extract_github` | README, stars, forks, last commit | GitHub URL |
|
|
30
|
+
| `extract_hackernews` | Stories + scores with timestamps | HN URL or search query |
|
|
31
|
+
| `extract_scholar` | Research papers ranked by year | Google Scholar URL |
|
|
32
|
+
| `extract_arxiv` | arXiv papers via official API | Search query or arXiv URL |
|
|
33
|
+
| `extract_reddit` | Subreddit posts + community sentiment | Subreddit URL |
|
|
34
|
+
| `extract_yc` | YC company listings by keyword | Topic keyword |
|
|
35
|
+
| `extract_producthunt` | Recent launches by topic | Topic or PH URL |
|
|
36
|
+
| `search_repos` | GitHub repos ranked by stars + recency | Search query |
|
|
37
|
+
| `package_trends` | npm/PyPI metadata + download stats | Package name(s) |
|
|
38
|
+
| `extract_finance` | Live stock data — price, P/E, 52w range | Ticker symbol(s) |
|
|
39
|
+
| `extract_landscape` | 6 sources in one call | Topic keyword |
|
|
40
|
+
| `extract_changelog` | Update history from any product/repo | GitHub URL, npm name, or website URL |
|
|
41
|
+
| `extract_govcontracts` | US federal contract awards | Company name, keyword, or NAICS code |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Command Reference
|
|
46
|
+
|
|
47
|
+
Copy and paste these directly into Claude.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Competitive Research
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Use extract_landscape with topic "AI data freshness"
|
|
55
|
+
```
|
|
56
|
+
*Full picture: who's funded, what's trending, what repos exist, what packages are moving.*
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Use search_repos with query "mcp server typescript"
|
|
60
|
+
```
|
|
61
|
+
*GitHub repos ranked by stars. See who's already building in your space.*
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Use extract_yc with query "developer infrastructure"
|
|
65
|
+
```
|
|
66
|
+
*Which YC companies are funded in your category.*
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Use extract_producthunt with url "developer tools"
|
|
70
|
+
```
|
|
71
|
+
*What launched this week in your category.*
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Market Intelligence
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Use extract_hackernews with url "https://hn.algolia.com/?q=model+context+protocol+2026"
|
|
79
|
+
```
|
|
80
|
+
*What the developer community is saying about a topic right now.*
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Use extract_reddit with url "https://www.reddit.com/r/MachineLearning/"
|
|
84
|
+
```
|
|
85
|
+
*Subreddit sentiment — what problems people are complaining about or excited about.*
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Use extract_finance with url "NVDA,MSFT,GOOGL"
|
|
89
|
+
```
|
|
90
|
+
*Live stock data for competitive set. Know the market before a pitch.*
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Use extract_govcontracts with url "AI infrastructure"
|
|
94
|
+
```
|
|
95
|
+
*Which companies are winning federal AI contracts. High-signal GTM data.*
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
Use extract_govcontracts with url "Palantir"
|
|
99
|
+
```
|
|
100
|
+
*How much government business a specific company is doing — and when.*
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Job Market Intelligence
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Use search_jobs with query "AI engineer" location "remote" max_age_days 7
|
|
108
|
+
```
|
|
109
|
+
*Fresh job listings only. Every result has a freshness badge — never apply to a closed role again.*
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Use search_jobs with query "typescript developer" remote_only true
|
|
113
|
+
```
|
|
114
|
+
*Remote-only filtered results with publish dates.*
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Use extract_govcontracts with url "541511"
|
|
118
|
+
```
|
|
119
|
+
*NAICS 541511 = Custom Computer Programming Services. See who's hiring via contract awards.*
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Developer Ecosystem Research
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Use package_trends with url "mcp,wrangler,anthropic"
|
|
127
|
+
```
|
|
128
|
+
*npm download trends for multiple packages. See what's gaining traction.*
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Use extract_changelog with url "https://github.com/anthropics/anthropic-sdk-python"
|
|
132
|
+
```
|
|
133
|
+
*Latest releases with dates. Know exactly when a dependency shipped a change.*
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Use extract_changelog with url "freshcontext-mcp"
|
|
137
|
+
```
|
|
138
|
+
*npm package changelog — version history and release cadence.*
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Use extract_changelog with url "https://linear.app"
|
|
142
|
+
```
|
|
143
|
+
*Auto-discovers the changelog page and extracts update history.*
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Use search_repos with query "cloudflare workers MCP 2026"
|
|
147
|
+
```
|
|
148
|
+
*What's being built on a platform right now.*
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Academic & Research Intelligence
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Use extract_arxiv with url "https://arxiv.org/search/?query=model+context+protocol&searchtype=all"
|
|
156
|
+
```
|
|
157
|
+
*Latest papers on a topic with submission dates.*
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
Use extract_scholar with url "https://scholar.google.com/scholar?q=AI+agent+grounding"
|
|
161
|
+
```
|
|
162
|
+
*Google Scholar results ranked by recency.*
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Full Research Workflows
|
|
167
|
+
|
|
168
|
+
#### Validate a startup idea
|
|
169
|
+
```
|
|
170
|
+
1. Use extract_landscape with topic "[your idea]"
|
|
171
|
+
2. Use extract_yc with query "[your idea]"
|
|
172
|
+
3. Use extract_govcontracts with url "[your idea keyword]"
|
|
173
|
+
4. Use extract_hackernews with url "https://hn.algolia.com/?q=[your+idea]"
|
|
174
|
+
```
|
|
175
|
+
*Tells you: who's funded, what's trending, is there government demand, what do developers think.*
|
|
176
|
+
|
|
177
|
+
#### Research a company before a meeting
|
|
178
|
+
```
|
|
179
|
+
1. Use extract_github with url "https://github.com/[company]"
|
|
180
|
+
2. Use extract_changelog with url "https://github.com/[company]/[main-repo]"
|
|
181
|
+
3. Use extract_govcontracts with url "[company name]"
|
|
182
|
+
4. Use extract_finance with url "[ticker]"
|
|
183
|
+
```
|
|
184
|
+
*Tells you: how active is their engineering team, what did they ship recently, do they have government contracts, what's the stock doing.*
|
|
185
|
+
|
|
186
|
+
#### Find your next job
|
|
187
|
+
```
|
|
188
|
+
1. Use search_jobs with query "[your role]" max_age_days 7
|
|
189
|
+
2. Use extract_govcontracts with url "[target company]"
|
|
190
|
+
3. Use extract_changelog with url "https://github.com/[target-company]/[repo]"
|
|
191
|
+
```
|
|
192
|
+
*Fresh listings only + proof the company is growing + proof they're actively shipping.*
|
|
193
|
+
|
|
194
|
+
#### Track a competitor
|
|
195
|
+
```
|
|
196
|
+
1. Use extract_changelog with url "https://[competitor].com"
|
|
197
|
+
2. Use extract_github with url "https://github.com/[competitor]/[repo]"
|
|
198
|
+
3. Use package_trends with url "[competitor-npm-package]"
|
|
199
|
+
4. Use extract_hackernews with url "https://hn.algolia.com/?q=[competitor+name]"
|
|
200
|
+
```
|
|
201
|
+
*When did they last ship? Is their repo active? Are downloads growing? What is the community saying?*
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Who This Is For
|
|
206
|
+
|
|
207
|
+
### Jobseekers
|
|
208
|
+
The tool FreshContext was built for. Stop applying to roles that closed months ago. Every job listing comes with a freshness badge — green means posted this week, red means posted 60+ days ago. Filter by `max_age_days` to only see roles posted recently.
|
|
209
|
+
|
|
210
|
+
**Key tools:** `search_jobs`, `extract_govcontracts`, `extract_changelog`
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### Founders & Product Teams
|
|
215
|
+
Validate ideas, track competitors, monitor your ecosystem. `extract_landscape` gives you a full competitive picture in one call — YC funding, GitHub activity, HN sentiment, Product Hunt launches, npm traction, all timestamped.
|
|
216
|
+
|
|
217
|
+
**Key tools:** `extract_landscape`, `extract_yc`, `extract_changelog`, `extract_hackernews`
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### GTM & Sales Teams
|
|
222
|
+
Government contracts are buying intent signals. A company that just won a $5M DoD contract is hiring, spending, and building. FreshContext is the only MCP server with access to USASpending.gov contract data.
|
|
223
|
+
|
|
224
|
+
**Key tools:** `extract_govcontracts`, `extract_landscape`, `extract_finance`
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
### Investors & Analysts
|
|
229
|
+
Track portfolio companies, find signals before they're obvious, monitor competitor funding. `extract_govcontracts` shows government contract pipeline. `extract_changelog` shows engineering velocity. `extract_finance` gives live market data.
|
|
230
|
+
|
|
231
|
+
**Key tools:** `extract_govcontracts`, `extract_landscape`, `extract_finance`, `extract_changelog`
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Researchers & Academics
|
|
236
|
+
Fresh papers from arXiv and Google Scholar, ranked by date. Never cite a paper without knowing exactly when it was published and retrieved.
|
|
237
|
+
|
|
238
|
+
**Key tools:** `extract_arxiv`, `extract_scholar`, `extract_hackernews`
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### Developers & DevRel Teams
|
|
243
|
+
Monitor your dependencies, track ecosystem traction, see what's getting attention in the community. `extract_changelog` works on any GitHub repo, npm package, or website that has a changelog.
|
|
244
|
+
|
|
245
|
+
**Key tools:** `extract_changelog`, `package_trends`, `search_repos`, `extract_github`
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Understanding the FreshContext Envelope
|
|
250
|
+
|
|
251
|
+
Every result is wrapped like this:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
[FRESHCONTEXT]
|
|
255
|
+
Source: https://github.com/owner/repo
|
|
256
|
+
Published: 2026-03-10
|
|
257
|
+
Retrieved: 2026-03-17T09:19:00Z
|
|
258
|
+
Confidence: high
|
|
259
|
+
---
|
|
260
|
+
... content ...
|
|
261
|
+
[/FRESHCONTEXT]
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Confidence levels:**
|
|
265
|
+
- `high` — date came from a structured API field. Reliable.
|
|
266
|
+
- `medium` — date inferred from page signals or URL patterns. Likely correct.
|
|
267
|
+
- `low` — no date signal found. Treat with caution.
|
|
268
|
+
|
|
269
|
+
The gap between `Published` and `Retrieved` is your staleness indicator. If a job was published 90 days ago and retrieved today, the role is likely filled. If a GitHub repo was last pushed 2 years ago, the project may be abandoned.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Freshness Score (coming in v0.4.0)
|
|
274
|
+
|
|
275
|
+
A numeric score from 0–100 will be added to every result:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
freshness_score = max(0, 100 - (days_since_retrieved × decay_rate))
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Different data types decay at different rates — financial data goes stale faster than academic papers. You'll be able to filter results by `freshness_score > 70` directly in your prompts.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## The Standard
|
|
286
|
+
|
|
287
|
+
FreshContext implements the **FreshContext Specification v1.0** — an open standard for data freshness envelopes. See `FRESHCONTEXT_SPEC.md` for the full specification.
|
|
288
|
+
|
|
289
|
+
Any tool or agent that wraps its retrieved data in the FreshContext envelope is **FreshContext-compatible**.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
*Built by Prince Gabriel — Grootfontein, Namibia 🇳🇦*
|
|
294
|
+
*"The work isn't gone. It's just waiting to be continued."*
|
|
@@ -1,63 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Government Contracts adapter — fetches awarded contract data from USASpending.gov
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* For GTM teams, VC investors, and competitive researchers, knowing when a
|
|
7
|
-
* company wins a government contract is a high-signal buying intent indicator.
|
|
8
|
-
* A company that just won a $2M DoD contract is hiring, spending, and building.
|
|
4
|
+
* No other MCP server has this. USASpending.gov is the official US Treasury
|
|
5
|
+
* database of all federal contract awards. Updated daily.
|
|
9
6
|
*
|
|
10
7
|
* Accepts:
|
|
11
|
-
* - Company name: "
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* - A URL: https://api.usaspending.gov/... → direct API call
|
|
16
|
-
*
|
|
17
|
-
* Data source: USASpending.gov public API (no API key required)
|
|
18
|
-
* Coverage: All US federal contracts, grants, and awards
|
|
19
|
-
* Freshness: Updated daily by the US Treasury
|
|
20
|
-
*
|
|
21
|
-
* What it returns:
|
|
22
|
-
* - Award recipient name and location
|
|
23
|
-
* - Contract amount (obligated)
|
|
24
|
-
* - Award date (high confidence timestamp)
|
|
25
|
-
* - Awarding agency and sub-agency
|
|
26
|
-
* - Contract description / award title
|
|
27
|
-
* - NAICS code and description
|
|
28
|
-
* - Period of performance dates
|
|
8
|
+
* - Company name: "Palantir" → contracts awarded to that company
|
|
9
|
+
* - Keyword: "AI infrastructure" → contracts with that keyword in description
|
|
10
|
+
* - NAICS code: "541511" → all software publisher contracts
|
|
11
|
+
* - Direct URL: https://api.usaspending.gov/... → direct API call
|
|
29
12
|
*/
|
|
30
13
|
function sanitize(s) {
|
|
31
14
|
return s.replace(/[^\x20-\x7E]/g, "").trim();
|
|
32
15
|
}
|
|
33
16
|
function formatUSD(amount) {
|
|
34
|
-
if (amount === null || isNaN(amount))
|
|
17
|
+
if (amount === null || amount === undefined || isNaN(amount))
|
|
35
18
|
return "N/A";
|
|
36
|
-
|
|
19
|
+
const abs = Math.abs(amount);
|
|
20
|
+
if (abs >= 1_000_000_000)
|
|
21
|
+
return `$${(amount / 1_000_000_000).toFixed(2)}B`;
|
|
22
|
+
if (abs >= 1_000_000)
|
|
37
23
|
return `$${(amount / 1_000_000).toFixed(2)}M`;
|
|
38
|
-
if (
|
|
24
|
+
if (abs >= 1_000)
|
|
39
25
|
return `$${(amount / 1_000).toFixed(1)}K`;
|
|
40
26
|
return `$${amount.toFixed(0)}`;
|
|
41
27
|
}
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
const HEADERS = {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"Accept": "application/json",
|
|
31
|
+
"User-Agent": "Mozilla/5.0 (compatible; freshcontext-mcp/1.0; +https://github.com/PrinceGabriel-lgtm/freshcontext-mcp)",
|
|
32
|
+
};
|
|
33
|
+
async function fetchJSON(url, body) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeout = setTimeout(() => controller.abort(), 20000);
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: body ? "POST" : "GET",
|
|
39
|
+
headers: HEADERS,
|
|
40
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text().catch(() => "");
|
|
45
|
+
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
46
|
+
}
|
|
47
|
+
return await res.json();
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ─── Search by recipient name using autocomplete then awards ─────────────────
|
|
54
|
+
async function searchByRecipient(name, maxLength) {
|
|
55
|
+
// Step 1: Use autocomplete to get the exact recipient name USASpending knows
|
|
56
|
+
let recipientName = name;
|
|
57
|
+
try {
|
|
58
|
+
const autoRes = await fetchJSON("https://api.usaspending.gov/api/v2/autocomplete/recipient/", { search_text: name, limit: 1 });
|
|
59
|
+
if (autoRes.results?.length) {
|
|
60
|
+
recipientName = autoRes.results[0].recipient_name;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Use original name if autocomplete fails
|
|
65
|
+
}
|
|
66
|
+
// Step 2: Search awards with the resolved recipient name
|
|
44
67
|
const body = {
|
|
45
68
|
filters: {
|
|
46
|
-
recipient_search_text: [
|
|
47
|
-
time_period: [
|
|
48
|
-
{
|
|
69
|
+
recipient_search_text: [recipientName],
|
|
70
|
+
time_period: [{
|
|
49
71
|
start_date: new Date(Date.now() - 365 * 2 * 86400000).toISOString().slice(0, 10),
|
|
50
72
|
end_date: new Date().toISOString().slice(0, 10),
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
award_type_codes: ["A", "B", "C", "D"], // contracts only
|
|
73
|
+
}],
|
|
74
|
+
award_type_codes: ["A", "B", "C", "D"],
|
|
54
75
|
},
|
|
55
76
|
fields: [
|
|
56
|
-
"Award_ID", "Recipient_Name", "Award_Amount",
|
|
77
|
+
"Award_ID", "Recipient_Name", "Award_Amount",
|
|
57
78
|
"Award_Date", "Start_Date", "End_Date",
|
|
58
79
|
"Awarding_Agency_Name", "Awarding_Sub_Agency_Name",
|
|
59
|
-
"
|
|
60
|
-
"naics_code", "naics_description",
|
|
80
|
+
"Description", "recipient_location_state_name",
|
|
81
|
+
"recipient_location_city_name", "naics_code", "naics_description",
|
|
61
82
|
],
|
|
62
83
|
page: 1,
|
|
63
84
|
limit: 10,
|
|
@@ -65,41 +86,33 @@ async function searchByRecipient(query, maxLength) {
|
|
|
65
86
|
order: "desc",
|
|
66
87
|
subawards: false,
|
|
67
88
|
};
|
|
68
|
-
const
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: { "Content-Type": "application/json", "User-Agent": "freshcontext-mcp" },
|
|
71
|
-
body: JSON.stringify(body),
|
|
72
|
-
});
|
|
73
|
-
if (!res.ok)
|
|
74
|
-
throw new Error(`USASpending API error: ${res.status}`);
|
|
75
|
-
const data = await res.json();
|
|
89
|
+
const data = await fetchJSON("https://api.usaspending.gov/api/v2/search/spending_by_award/", body);
|
|
76
90
|
if (!data.results?.length) {
|
|
77
91
|
return {
|
|
78
|
-
raw: `No federal contracts found for "${
|
|
92
|
+
raw: `No federal contracts found for "${name}" (searched as "${recipientName}") in the last 2 years.\n\nTips:\n- Try the full legal company name (e.g. "Palantir Technologies Inc")\n- Try a keyword search instead (e.g. "AI data analytics")\n- Try a NAICS code (e.g. 541511 for software)`,
|
|
79
93
|
content_date: null,
|
|
80
94
|
freshness_confidence: "high",
|
|
81
95
|
};
|
|
82
96
|
}
|
|
83
|
-
return formatResults(data.results, `Federal contracts — ${
|
|
97
|
+
return formatResults(data.results, `Federal contracts — ${recipientName}`, maxLength);
|
|
84
98
|
}
|
|
85
99
|
// ─── Search by keyword ────────────────────────────────────────────────────────
|
|
86
100
|
async function searchByKeyword(keyword, maxLength) {
|
|
87
101
|
const body = {
|
|
88
102
|
filters: {
|
|
89
103
|
keywords: [keyword],
|
|
90
|
-
time_period: [
|
|
91
|
-
{
|
|
104
|
+
time_period: [{
|
|
92
105
|
start_date: new Date(Date.now() - 365 * 86400000).toISOString().slice(0, 10),
|
|
93
106
|
end_date: new Date().toISOString().slice(0, 10),
|
|
94
|
-
},
|
|
95
|
-
],
|
|
107
|
+
}],
|
|
96
108
|
award_type_codes: ["A", "B", "C", "D"],
|
|
97
109
|
},
|
|
98
110
|
fields: [
|
|
99
|
-
"Award_ID", "Recipient_Name", "Award_Amount",
|
|
111
|
+
"Award_ID", "Recipient_Name", "Award_Amount",
|
|
100
112
|
"Award_Date", "Start_Date", "End_Date",
|
|
101
113
|
"Awarding_Agency_Name", "Awarding_Sub_Agency_Name",
|
|
102
|
-
"
|
|
114
|
+
"Description", "recipient_location_state_name",
|
|
115
|
+
"naics_code", "naics_description",
|
|
103
116
|
],
|
|
104
117
|
page: 1,
|
|
105
118
|
limit: 10,
|
|
@@ -107,17 +120,10 @@ async function searchByKeyword(keyword, maxLength) {
|
|
|
107
120
|
order: "desc",
|
|
108
121
|
subawards: false,
|
|
109
122
|
};
|
|
110
|
-
const
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: { "Content-Type": "application/json", "User-Agent": "freshcontext-mcp" },
|
|
113
|
-
body: JSON.stringify(body),
|
|
114
|
-
});
|
|
115
|
-
if (!res.ok)
|
|
116
|
-
throw new Error(`USASpending keyword search error: ${res.status}`);
|
|
117
|
-
const data = await res.json();
|
|
123
|
+
const data = await fetchJSON("https://api.usaspending.gov/api/v2/search/spending_by_award/", body);
|
|
118
124
|
if (!data.results?.length) {
|
|
119
125
|
return {
|
|
120
|
-
raw: `No federal contracts found matching
|
|
126
|
+
raw: `No federal contracts found matching "${keyword}" in the last year.`,
|
|
121
127
|
content_date: null,
|
|
122
128
|
freshness_confidence: "high",
|
|
123
129
|
};
|
|
@@ -128,35 +134,30 @@ async function searchByKeyword(keyword, maxLength) {
|
|
|
128
134
|
function formatResults(results, title, maxLength) {
|
|
129
135
|
const lines = [title, ""];
|
|
130
136
|
results.forEach((award, i) => {
|
|
131
|
-
const desc = sanitize(award.Description ?? "No description");
|
|
137
|
+
const desc = sanitize(award.Description ?? "No description").slice(0, 300);
|
|
132
138
|
const location = [award.recipient_location_city_name, award.recipient_location_state_name]
|
|
133
139
|
.filter(Boolean).join(", ") || "N/A";
|
|
134
140
|
lines.push(`[${i + 1}] ${sanitize(award.Recipient_Name ?? "Unknown")}`);
|
|
135
|
-
lines.push(` Amount: ${formatUSD(award.Award_Amount)}`);
|
|
141
|
+
lines.push(` Amount: ${formatUSD(award.Award_Amount ?? null)}`);
|
|
136
142
|
lines.push(` Awarded: ${award.Award_Date?.slice(0, 10) ?? "unknown"}`);
|
|
137
143
|
lines.push(` Period: ${award.Start_Date?.slice(0, 10) ?? "?"} → ${award.End_Date?.slice(0, 10) ?? "?"}`);
|
|
138
144
|
lines.push(` Agency: ${sanitize(award.Awarding_Agency_Name ?? "N/A")}`);
|
|
139
|
-
if (award.Awarding_Sub_Agency_Name
|
|
145
|
+
if (award.Awarding_Sub_Agency_Name !== award.Awarding_Agency_Name && award.Awarding_Sub_Agency_Name) {
|
|
140
146
|
lines.push(` Sub-agency: ${sanitize(award.Awarding_Sub_Agency_Name)}`);
|
|
141
147
|
}
|
|
142
148
|
if (award.naics_code) {
|
|
143
149
|
lines.push(` NAICS: ${award.naics_code} — ${sanitize(award.naics_description ?? "")}`);
|
|
144
150
|
}
|
|
145
151
|
lines.push(` Location: ${location}`);
|
|
146
|
-
lines.push(` Description: ${desc
|
|
152
|
+
lines.push(` Description: ${desc}`);
|
|
147
153
|
lines.push("");
|
|
148
154
|
});
|
|
149
155
|
const raw = lines.join("\n").slice(0, maxLength);
|
|
150
|
-
|
|
151
|
-
const dates = results
|
|
152
|
-
.map((r) => r.Award_Date)
|
|
153
|
-
.filter(Boolean)
|
|
154
|
-
.sort()
|
|
155
|
-
.reverse();
|
|
156
|
+
const dates = results.map(r => r.Award_Date).filter(Boolean).sort().reverse();
|
|
156
157
|
return {
|
|
157
158
|
raw,
|
|
158
159
|
content_date: dates[0] ?? null,
|
|
159
|
-
freshness_confidence: "high",
|
|
160
|
+
freshness_confidence: "high",
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
163
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
@@ -167,30 +168,30 @@ export async function govContractsAdapter(options) {
|
|
|
167
168
|
throw new Error("Query required: company name, keyword, or NAICS code");
|
|
168
169
|
// Direct API URL
|
|
169
170
|
if (input.startsWith("https://api.usaspending.gov")) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
const data = await fetchJSON(input);
|
|
172
|
+
return {
|
|
173
|
+
raw: JSON.stringify(data, null, 2).slice(0, maxLength),
|
|
174
|
+
content_date: new Date().toISOString(),
|
|
175
|
+
freshness_confidence: "high",
|
|
176
|
+
};
|
|
176
177
|
}
|
|
177
|
-
// NAICS code (6 digits)
|
|
178
|
+
// NAICS code (6 digits) — treat as keyword
|
|
178
179
|
if (/^\d{6}$/.test(input)) {
|
|
179
180
|
return searchByKeyword(input, maxLength);
|
|
180
181
|
}
|
|
181
|
-
//
|
|
182
|
+
// Multi-word input or known company name → try recipient first, fall back to keyword
|
|
182
183
|
try {
|
|
183
184
|
const result = await searchByRecipient(input, maxLength);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return result;
|
|
185
|
+
if (!result.raw.includes("No federal contracts found"))
|
|
186
|
+
return result;
|
|
187
|
+
// Fall back to keyword search
|
|
188
|
+
const kwResult = await searchByKeyword(input, maxLength);
|
|
189
|
+
if (!kwResult.raw.includes("No federal contracts found"))
|
|
190
|
+
return kwResult;
|
|
191
|
+
return result; // Return the "not found" message from recipient search
|
|
192
192
|
}
|
|
193
|
-
catch {
|
|
193
|
+
catch (err) {
|
|
194
|
+
// If recipient search fails entirely, try keyword
|
|
194
195
|
return searchByKeyword(input, maxLength);
|
|
195
196
|
}
|
|
196
197
|
}
|
package/package.json
CHANGED