freshcontext-mcp 0.3.11 → 0.3.12
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/.actor/Dockerfile +5 -3
- package/HANDOFF.md +184 -0
- package/SESSION_SAVE_V4.md +60 -0
- package/dist/adapters/gdelt.js +104 -0
- package/dist/adapters/secFilings.js +98 -0
- package/dist/server.js +91 -0
- package/package.json +1 -1
package/.actor/Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
FROM apify/actor-node:
|
|
1
|
+
FROM apify/actor-node:20
|
|
2
2
|
|
|
3
3
|
# Copy package files first for better Docker layer caching
|
|
4
4
|
COPY package*.json ./
|
|
@@ -6,9 +6,11 @@ COPY package*.json ./
|
|
|
6
6
|
# Install all dependencies including apify SDK
|
|
7
7
|
RUN npm install --include=dev
|
|
8
8
|
|
|
9
|
-
# Copy source and
|
|
9
|
+
# Copy source and pre-built dist
|
|
10
10
|
COPY . ./
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
# Rebuild TypeScript (dist/ from repo is fallback if this fails)
|
|
13
|
+
RUN npm run build || echo "Build had warnings, using pre-compiled dist/"
|
|
12
14
|
|
|
13
15
|
# Tell Apify to run the Actor entry point, not the MCP server
|
|
14
16
|
CMD ["node", "dist/apify.js"]
|
package/HANDOFF.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# FreshContext — Handoff Document
|
|
2
|
+
**Version:** 0.3.11
|
|
3
|
+
**Date:** 2026-03-20
|
|
4
|
+
**Author:** Immanuel Gabriel (Prince Gabriel), Grootfontein, Namibia
|
|
5
|
+
**Contact:** gimmanuel73@gmail.com
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What You Are Receiving
|
|
10
|
+
|
|
11
|
+
FreshContext is a web intelligence engine for AI agents. It wraps every piece of
|
|
12
|
+
retrieved web data in a structured freshness envelope — exact retrieval timestamp,
|
|
13
|
+
publication date estimate, freshness confidence (high/medium/low), and a 0-100
|
|
14
|
+
numeric score with domain-specific decay rates.
|
|
15
|
+
|
|
16
|
+
15 tools. No API keys required. Deployed globally on Cloudflare's edge. Listed on
|
|
17
|
+
Anthropic's official MCP Registry, npm, and Apify Store.
|
|
18
|
+
|
|
19
|
+
The two tools that exist nowhere else: extract_govcontracts (US federal contract
|
|
20
|
+
intelligence via USASpending.gov) and extract_changelog (product release velocity
|
|
21
|
+
from any GitHub repo, npm package, or website).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Services and Infrastructure
|
|
26
|
+
|
|
27
|
+
### 1. GitHub Repository
|
|
28
|
+
URL: https://github.com/PrinceGabriel-lgtm/freshcontext-mcp
|
|
29
|
+
Branch: main
|
|
30
|
+
Transfer method: GitHub Settings > Transfer ownership
|
|
31
|
+
What it contains: All source code, Dockerfile, specs, session saves, roadmap
|
|
32
|
+
|
|
33
|
+
### 2. npm Package
|
|
34
|
+
Package: freshcontext-mcp (v0.3.11)
|
|
35
|
+
URL: https://www.npmjs.com/package/freshcontext-mcp
|
|
36
|
+
Account: immanuel-gabriel on npmjs.com
|
|
37
|
+
Transfer method: npm owner add new-username freshcontext-mcp
|
|
38
|
+
Note: Published automatically via GitHub Actions on every push to main
|
|
39
|
+
|
|
40
|
+
### 3. Cloudflare Account
|
|
41
|
+
What lives here:
|
|
42
|
+
Worker: freshcontext-mcp (the live MCP endpoint)
|
|
43
|
+
D1: freshcontext-db (ID: d9898d65-f67e-4dcb-abdc-7f7b53f2d444)
|
|
44
|
+
KV: RATE_LIMITER and CACHE (IDs in wrangler.jsonc)
|
|
45
|
+
Cron: 0 */6 * * * (every 6 hours, runs automatically)
|
|
46
|
+
Endpoint: https://freshcontext-mcp.gimmanuel73.workers.dev/mcp
|
|
47
|
+
|
|
48
|
+
Transfer method: Add new account as Super Administrator, remove original.
|
|
49
|
+
D1 export: wrangler d1 export freshcontext-db --output=dump.sql
|
|
50
|
+
|
|
51
|
+
### 4. Apify Actor
|
|
52
|
+
Actor: prince_gabriel/freshcontext-mcp
|
|
53
|
+
URL: https://apify.com/prince_gabriel/freshcontext-mcp
|
|
54
|
+
Monetization: $50.00 per 1,000 results (Pay per event)
|
|
55
|
+
Transfer method: Re-publish under new Apify account
|
|
56
|
+
|
|
57
|
+
### 5. MCP Registry Listing
|
|
58
|
+
Entry: io.github.PrinceGabriel-lgtm/freshcontext
|
|
59
|
+
Config: server.json in the GitHub repo
|
|
60
|
+
Transfer method: Update server.json with new repo URL and re-submit
|
|
61
|
+
|
|
62
|
+
### 6. GitHub Actions CI/CD
|
|
63
|
+
File: .github/workflows/publish.yml
|
|
64
|
+
Action: On every push to main — npm ci > tsc > npm publish
|
|
65
|
+
Secret: NPM_TOKEN (granular access token from npmjs.com, renew annually)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Credentials Map (categories only — not values)
|
|
70
|
+
|
|
71
|
+
| Credential | Where Used | Location |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| API_KEY | Worker auth header | Cloudflare env var |
|
|
74
|
+
| ANTHROPIC_KEY | Synthesis/briefing endpoint | Cloudflare env var |
|
|
75
|
+
| GITHUB_TOKEN | GitHub API rate limit bypass | Cloudflare env var |
|
|
76
|
+
| NPM_TOKEN | GitHub Actions auto-publish | GitHub secret |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Codebase Map
|
|
81
|
+
|
|
82
|
+
src/server.ts — MCP stdio server, all 15 tools registered here
|
|
83
|
+
src/apify.ts — Apify Actor entry point (read input, call adapter, exit)
|
|
84
|
+
src/security.ts — URL validation, SSRF prevention
|
|
85
|
+
src/types.ts — FreshContext, AdapterResult, ExtractOptions interfaces
|
|
86
|
+
src/adapters/ — One file per data source (13 files)
|
|
87
|
+
changelog.ts — UNIQUE: GitHub Releases API + npm + auto-discover
|
|
88
|
+
govcontracts.ts — UNIQUE: USASpending.gov federal contract awards
|
|
89
|
+
src/tools/
|
|
90
|
+
freshnessStamp.ts — Score calculation, JSON form, text envelope
|
|
91
|
+
worker/src/worker.ts — Cloudflare Worker: 15 tools + KV cache + rate limit
|
|
92
|
+
+ D1 cron scraper + briefing formatter
|
|
93
|
+
.actor/Dockerfile — Apify build config (Node 20, runs dist/apify.js)
|
|
94
|
+
.actor/actor.json — Apify Actor metadata
|
|
95
|
+
FRESHCONTEXT_SPEC.md — The open standard (MIT license)
|
|
96
|
+
ROADMAP.md — 10-layer product vision
|
|
97
|
+
server.json — MCP Registry listing
|
|
98
|
+
.github/workflows/
|
|
99
|
+
publish.yml — GitHub Actions CI/CD
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## The 15 Tools
|
|
104
|
+
|
|
105
|
+
Standard (11):
|
|
106
|
+
extract_github, extract_hackernews, extract_scholar, extract_arxiv,
|
|
107
|
+
extract_reddit, extract_yc, extract_producthunt, search_repos,
|
|
108
|
+
package_trends, extract_finance, search_jobs
|
|
109
|
+
|
|
110
|
+
Composite landscapes (3):
|
|
111
|
+
extract_landscape — YC + GitHub + HN + Reddit + Product Hunt + npm
|
|
112
|
+
extract_gov_landscape — govcontracts + HN + GitHub + changelog
|
|
113
|
+
extract_finance_landscape — finance + HN + Reddit + GitHub + changelog
|
|
114
|
+
|
|
115
|
+
Unique — not in any other MCP server (2):
|
|
116
|
+
extract_changelog — release history from any repo, package, or website
|
|
117
|
+
extract_govcontracts — US federal contract awards from USASpending.gov
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## D1 Database Schema
|
|
122
|
+
|
|
123
|
+
watched_queries — 18 active monitored topics
|
|
124
|
+
id, adapter, query, label, filters, enabled, last_run_at
|
|
125
|
+
|
|
126
|
+
scrape_results — raw results, deduplicated by content hash
|
|
127
|
+
id, watched_query_id, adapter, query, raw_content, result_hash, is_new, scraped_at
|
|
128
|
+
|
|
129
|
+
briefings — formatted intelligence reports per cron run
|
|
130
|
+
id, user_id, summary, new_results_count, adapters_run, created_at
|
|
131
|
+
|
|
132
|
+
user_profiles — personalization data for briefing synthesis
|
|
133
|
+
id, name, skills, certifications, targets, location, context
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## The FreshContext Specification
|
|
138
|
+
|
|
139
|
+
FRESHCONTEXT_SPEC.md is the open standard, MIT license, authored March 2026.
|
|
140
|
+
Any implementation returning the [FRESHCONTEXT]...[/FRESHCONTEXT] envelope
|
|
141
|
+
or the structured JSON form with freshcontext.retrieved_at and
|
|
142
|
+
freshcontext.freshness_confidence is FreshContext-compatible.
|
|
143
|
+
|
|
144
|
+
The spec is the durable asset. The code is the reference implementation.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## What Keeps Running Without You
|
|
149
|
+
|
|
150
|
+
The Cloudflare cron fires every 6 hours automatically. Every run scrapes all 18
|
|
151
|
+
watched queries, deduplicates by content hash, stores new signals in D1, and
|
|
152
|
+
generates a briefing. The dataset accumulates indefinitely. No action required.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Pending Items at Time of Handoff
|
|
157
|
+
|
|
158
|
+
Apify Playwright — extract_reddit, extract_yc, extract_producthunt may error on
|
|
159
|
+
Apify. Fix: add RUN npx playwright install chromium --with-deps to .actor/Dockerfile.
|
|
160
|
+
|
|
161
|
+
Synthesis endpoint — /briefing/now is paused. Needs ANTHROPIC_KEY in Worker env
|
|
162
|
+
and credits loaded at console.anthropic.com. Infrastructure is fully built.
|
|
163
|
+
|
|
164
|
+
Agnost AI analytics — free MCP analytics offered by Apify. Sign up at app.agnost.ai,
|
|
165
|
+
add one line to server.ts. Gives tool call tracking and usage dashboards.
|
|
166
|
+
|
|
167
|
+
Apify GitHub Actions trigger — npm publish is automated but Apify rebuild is
|
|
168
|
+
manual. Add a workflow step calling Apify's API to auto-rebuild on push.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Outreach Status at Time of Handoff
|
|
173
|
+
|
|
174
|
+
19 confirmed delivered emails across US, Singapore, China, and Europe.
|
|
175
|
+
Cloudflare for Startups — awaiting reply (10 business day window).
|
|
176
|
+
GovTech Singapore — auto-acknowledged, reply expected by March 25, 2026.
|
|
177
|
+
All others — first contact sent, no replies yet.
|
|
178
|
+
|
|
179
|
+
HN post live: https://news.ycombinator.com/user?id=Prince-Gabriel
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
*"The work isn't gone. It's just waiting to be continued."*
|
|
184
|
+
*— Prince Gabriel, Grootfontein, Namibia*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# FreshContext — Session Save V4
|
|
2
|
+
**Date:** 2026-03-20
|
|
3
|
+
**npm:** freshcontext-mcp@0.3.11
|
|
4
|
+
**Tools:** 15 live → building to 18
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What Was Done This Session
|
|
9
|
+
|
|
10
|
+
- Apify Actor fixed: Node 20 base image, clean build confirmed
|
|
11
|
+
- HANDOFF.md created and pushed
|
|
12
|
+
- Command reference HTML updated with example output section
|
|
13
|
+
- Intelligence report (Anthropic/OpenAI/Palantir) confirmed as best product demo asset
|
|
14
|
+
- SESSION_SAVE_V3.md written
|
|
15
|
+
- Strategic discussion: keep the niche, deepen it — spec adoption > more tools
|
|
16
|
+
- Identified two new unique adapters: extract_sec_filings + extract_gdelt
|
|
17
|
+
- Composite: extract_company_landscape (5 sources: SEC + govcontracts + GDELT + changelog + finance)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Next Build — NOW IN PROGRESS
|
|
22
|
+
|
|
23
|
+
### extract_sec_filings
|
|
24
|
+
Source: SEC EDGAR full-text search (efts.sec.gov/LATEST/search-index)
|
|
25
|
+
No auth. Free. Real-time.
|
|
26
|
+
Returns: 8-K filings — legally mandated material event disclosures
|
|
27
|
+
CEO changes, major contracts, acquisitions, breaches, regulatory actions
|
|
28
|
+
File: src/adapters/secFilings.ts
|
|
29
|
+
|
|
30
|
+
### extract_gdelt
|
|
31
|
+
Source: GDELT Project (api.gdeltproject.org/api/v2/doc/doc)
|
|
32
|
+
No auth. Free. Updated every 15 minutes.
|
|
33
|
+
Returns: Structured global news events with tone, goldstein scale, actor tags
|
|
34
|
+
File: src/adapters/gdelt.ts
|
|
35
|
+
|
|
36
|
+
### extract_company_landscape (composite)
|
|
37
|
+
5 sources in parallel:
|
|
38
|
+
1. extract_sec_filings — legal disclosures
|
|
39
|
+
2. extract_govcontracts — federal contract footprint
|
|
40
|
+
3. extract_gdelt — global news events
|
|
41
|
+
4. extract_changelog — shipping velocity
|
|
42
|
+
5. extract_finance — market pricing
|
|
43
|
+
File: register in src/server.ts
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Moat Summary (all 4 unique adapters)
|
|
48
|
+
extract_changelog — release history from any repo/package/site
|
|
49
|
+
extract_govcontracts — US federal contract awards (USASpending.gov)
|
|
50
|
+
extract_sec_filings — 8-K material event disclosures (SEC EDGAR)
|
|
51
|
+
extract_gdelt — global structured news events (GDELT Project)
|
|
52
|
+
|
|
53
|
+
These 4 exist in no other MCP server. All free. All no-auth.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Resume Prompt
|
|
58
|
+
"I'm building freshcontext-mcp. Just built extract_sec_filings and extract_gdelt.
|
|
59
|
+
Need to register them in server.ts and build extract_company_landscape composite.
|
|
60
|
+
See SESSION_SAVE_V4.md."
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GDELT adapter — fetches structured global news intelligence from the GDELT Project
|
|
3
|
+
*
|
|
4
|
+
* No other MCP server has this. GDELT monitors broadcast, print, and web news
|
|
5
|
+
* from every country in 100+ languages, updated every 15 minutes. Free, no auth.
|
|
6
|
+
*
|
|
7
|
+
* Returns structured geopolitical intelligence — not just headlines, but event codes,
|
|
8
|
+
* actor tags, tone scores, goldstein scale (impact measure), location, timestamp.
|
|
9
|
+
*
|
|
10
|
+
* API: https://api.gdeltproject.org/api/v2/doc/doc
|
|
11
|
+
*/
|
|
12
|
+
const HEADERS = {
|
|
13
|
+
"Accept": "application/json",
|
|
14
|
+
"User-Agent": "freshcontext-mcp/1.0 contact@freshcontext.dev",
|
|
15
|
+
};
|
|
16
|
+
function parseGdeltDate(raw) {
|
|
17
|
+
if (!raw)
|
|
18
|
+
return null;
|
|
19
|
+
// Format: 20260320T093000Z → 2026-03-20T09:30:00Z
|
|
20
|
+
const m = raw.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z?$/);
|
|
21
|
+
if (!m)
|
|
22
|
+
return null;
|
|
23
|
+
return `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`;
|
|
24
|
+
}
|
|
25
|
+
async function fetchGdelt(query, maxRecords = 15) {
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
query: query,
|
|
28
|
+
mode: "artlist",
|
|
29
|
+
maxrecords: String(maxRecords),
|
|
30
|
+
format: "json",
|
|
31
|
+
timespan: "1month",
|
|
32
|
+
sort: "DateDesc",
|
|
33
|
+
});
|
|
34
|
+
const url = `https://api.gdeltproject.org/api/v2/doc/doc?${params}`;
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timeout = setTimeout(() => controller.abort(), 20000);
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(url, {
|
|
39
|
+
headers: HEADERS,
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const text = await res.text().catch(() => "");
|
|
44
|
+
throw new Error(`GDELT HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
45
|
+
}
|
|
46
|
+
const text = await res.text();
|
|
47
|
+
// GDELT sometimes returns empty body or non-JSON
|
|
48
|
+
if (!text.trim() || text.trim() === "null") {
|
|
49
|
+
return { articles: [] };
|
|
50
|
+
}
|
|
51
|
+
return JSON.parse(text);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function formatArticles(data, query, maxLength) {
|
|
58
|
+
const articles = data.articles ?? [];
|
|
59
|
+
if (!articles.length) {
|
|
60
|
+
return {
|
|
61
|
+
raw: `No GDELT news events found for "${query}" in the last month.\n\nTips:\n- Try a broader keyword: "artificial intelligence" instead of "Claude AI"\n- Try a company name: "Palantir"\n- GDELT covers global news — try in English for best results`,
|
|
62
|
+
content_date: null,
|
|
63
|
+
freshness_confidence: "high",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const lines = [
|
|
67
|
+
`GDELT Global News Intelligence — ${query}`,
|
|
68
|
+
`${articles.length} articles from global news sources (last 30 days)`,
|
|
69
|
+
"",
|
|
70
|
+
];
|
|
71
|
+
let latestDate = null;
|
|
72
|
+
articles.forEach((article, i) => {
|
|
73
|
+
const date = parseGdeltDate(article.seendate);
|
|
74
|
+
const title = (article.title ?? "No title").slice(0, 200);
|
|
75
|
+
const domain = article.domain ?? "unknown";
|
|
76
|
+
const country = article.sourcecountry ?? "N/A";
|
|
77
|
+
const language = article.language ?? "N/A";
|
|
78
|
+
const url = article.url ?? "";
|
|
79
|
+
if (date && (!latestDate || date > latestDate))
|
|
80
|
+
latestDate = date;
|
|
81
|
+
lines.push(`[${i + 1}] ${title}`);
|
|
82
|
+
lines.push(` Source: ${domain} (${country})`);
|
|
83
|
+
lines.push(` Language: ${language}`);
|
|
84
|
+
lines.push(` Date: ${date ?? article.seendate ?? "unknown"}`);
|
|
85
|
+
if (url)
|
|
86
|
+
lines.push(` URL: ${url.slice(0, 200)}`);
|
|
87
|
+
lines.push("");
|
|
88
|
+
});
|
|
89
|
+
lines.push(`Source: GDELT Project — https://www.gdeltproject.org`);
|
|
90
|
+
lines.push(`Coverage: 100+ languages, every country, updated every 15 minutes`);
|
|
91
|
+
return {
|
|
92
|
+
raw: lines.join("\n").slice(0, maxLength),
|
|
93
|
+
content_date: latestDate,
|
|
94
|
+
freshness_confidence: "high",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export async function gdeltAdapter(options) {
|
|
98
|
+
const query = (options.url ?? "").trim();
|
|
99
|
+
const maxLength = options.maxLength ?? 6000;
|
|
100
|
+
if (!query)
|
|
101
|
+
throw new Error("Query required: company name, topic, or keyword");
|
|
102
|
+
const data = await fetchGdelt(query);
|
|
103
|
+
return formatArticles(data, query, maxLength);
|
|
104
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEC EDGAR adapter — fetches 8-K filings from the SEC's full-text search API
|
|
3
|
+
*
|
|
4
|
+
* No other MCP server has this. 8-K filings are legally mandated disclosures
|
|
5
|
+
* of material corporate events — CEO changes, acquisitions, data breaches,
|
|
6
|
+
* major contracts, regulatory actions. Filed within 4 business days of the event.
|
|
7
|
+
*
|
|
8
|
+
* This is the most reliable early-warning signal for corporate events in existence.
|
|
9
|
+
* Free, no auth, updated in real time.
|
|
10
|
+
*
|
|
11
|
+
* API: https://efts.sec.gov/LATEST/search-index
|
|
12
|
+
*/
|
|
13
|
+
const HEADERS = {
|
|
14
|
+
"Accept": "application/json",
|
|
15
|
+
"User-Agent": "freshcontext-mcp/1.0 contact@freshcontext.dev",
|
|
16
|
+
};
|
|
17
|
+
async function fetchSecFilings(query, maxResults = 10) {
|
|
18
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
19
|
+
const oneYearAgo = new Date(Date.now() - 365 * 86400000).toISOString().slice(0, 10);
|
|
20
|
+
const params = new URLSearchParams({
|
|
21
|
+
q: `"${query}"`,
|
|
22
|
+
forms: "8-K",
|
|
23
|
+
dateRange: "custom",
|
|
24
|
+
startdt: oneYearAgo,
|
|
25
|
+
enddt: today,
|
|
26
|
+
hits: String(maxResults),
|
|
27
|
+
});
|
|
28
|
+
const url = `https://efts.sec.gov/LATEST/search-index?${params}`;
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timeout = setTimeout(() => controller.abort(), 20000);
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
headers: HEADERS,
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const text = await res.text().catch(() => "");
|
|
38
|
+
throw new Error(`SEC EDGAR HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
39
|
+
}
|
|
40
|
+
return await res.json();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function formatFilings(data, query, maxLength) {
|
|
47
|
+
const hits = data.hits?.hits ?? [];
|
|
48
|
+
const total = data.hits?.total?.value ?? 0;
|
|
49
|
+
if (!hits.length) {
|
|
50
|
+
return {
|
|
51
|
+
raw: `No 8-K filings found for "${query}" in the last year.\n\nTips:\n- Try the full legal company name: "Palantir Technologies"\n- Try a ticker-like keyword: "PLTR"\n- 8-K filings are only for public companies`,
|
|
52
|
+
content_date: null,
|
|
53
|
+
freshness_confidence: "high",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const lines = [
|
|
57
|
+
`SEC 8-K Filings — ${query}`,
|
|
58
|
+
`${total.toLocaleString()} total filings found (showing ${hits.length})`,
|
|
59
|
+
"",
|
|
60
|
+
];
|
|
61
|
+
let latestDate = null;
|
|
62
|
+
hits.forEach((hit, i) => {
|
|
63
|
+
const src = hit._source ?? {};
|
|
64
|
+
const entityName = src.entity_name ?? hit.entity_name ?? "Unknown";
|
|
65
|
+
const fileDate = src.filed_at?.slice(0, 10) ?? hit.file_date ?? "unknown";
|
|
66
|
+
const period = src.period_of_report?.slice(0, 10) ?? hit.period_of_report ?? "unknown";
|
|
67
|
+
const formType = src.form_type ?? hit.form_type ?? "8-K";
|
|
68
|
+
const location = [src.biz_location, src.inc_states].filter(Boolean).join(" / ") || "N/A";
|
|
69
|
+
const filingId = hit._id ?? "N/A";
|
|
70
|
+
if (fileDate && fileDate !== "unknown") {
|
|
71
|
+
if (!latestDate || fileDate > latestDate)
|
|
72
|
+
latestDate = fileDate;
|
|
73
|
+
}
|
|
74
|
+
lines.push(`[${i + 1}] ${entityName}`);
|
|
75
|
+
lines.push(` Form: ${formType}`);
|
|
76
|
+
lines.push(` Filed: ${fileDate}`);
|
|
77
|
+
lines.push(` Period: ${period}`);
|
|
78
|
+
lines.push(` Location: ${location}`);
|
|
79
|
+
lines.push(` Filing ID: ${filingId}`);
|
|
80
|
+
lines.push(` View: https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&company=${encodeURIComponent(entityName)}&type=8-K&dateb=&owner=include&count=10`);
|
|
81
|
+
lines.push("");
|
|
82
|
+
});
|
|
83
|
+
lines.push(`Source: SEC EDGAR — https://efts.sec.gov/LATEST/search-index`);
|
|
84
|
+
lines.push(`Note: 8-K filings are legally mandated disclosures of material events filed within 4 business days.`);
|
|
85
|
+
return {
|
|
86
|
+
raw: lines.join("\n").slice(0, maxLength),
|
|
87
|
+
content_date: latestDate,
|
|
88
|
+
freshness_confidence: "high",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export async function secFilingsAdapter(options) {
|
|
92
|
+
const query = (options.url ?? "").trim();
|
|
93
|
+
const maxLength = options.maxLength ?? 6000;
|
|
94
|
+
if (!query)
|
|
95
|
+
throw new Error("Company name or keyword required");
|
|
96
|
+
const data = await fetchSecFilings(query);
|
|
97
|
+
return formatFilings(data, query, maxLength);
|
|
98
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -13,6 +13,8 @@ import { financeAdapter } from "./adapters/finance.js";
|
|
|
13
13
|
import { jobsAdapter } from "./adapters/jobs.js";
|
|
14
14
|
import { changelogAdapter } from "./adapters/changelog.js";
|
|
15
15
|
import { govContractsAdapter } from "./adapters/govcontracts.js";
|
|
16
|
+
import { secFilingsAdapter } from "./adapters/secFilings.js";
|
|
17
|
+
import { gdeltAdapter } from "./adapters/gdelt.js";
|
|
16
18
|
import { stampFreshness, formatForLLM } from "./tools/freshnessStamp.js";
|
|
17
19
|
import { formatSecurityError } from "./security.js";
|
|
18
20
|
const server = new McpServer({
|
|
@@ -317,6 +319,95 @@ server.registerTool("extract_finance_landscape", {
|
|
|
317
319
|
].join("\n\n");
|
|
318
320
|
return { content: [{ type: "text", text: combined }] };
|
|
319
321
|
});
|
|
322
|
+
// ─── Tool: extract_sec_filings ─────────────────────────────────────────────
|
|
323
|
+
// 8-K filings = legally mandated material event disclosures. CEO changes,
|
|
324
|
+
// acquisitions, breaches, major contracts, regulatory actions — all filed
|
|
325
|
+
// within 4 business days. Most reliable early-warning corporate signal in existence.
|
|
326
|
+
// Unique: no other MCP server has this.
|
|
327
|
+
server.registerTool("extract_sec_filings", {
|
|
328
|
+
description: "Fetch SEC 8-K filings for any public company from the SEC EDGAR full-text search API. 8-K filings are legally mandated disclosures of material corporate events — CEO changes, acquisitions, data breaches, major contracts, regulatory actions — filed within 4 business days. Free, no auth, real-time. Pass a company name, ticker, or keyword. Unique: not available in any other MCP server.",
|
|
329
|
+
inputSchema: z.object({
|
|
330
|
+
url: z.string().describe("Company name, ticker, or keyword e.g. 'Palantir', 'PLTR', 'artificial intelligence'"),
|
|
331
|
+
max_length: z.number().optional().default(6000),
|
|
332
|
+
}),
|
|
333
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
334
|
+
}, async ({ url, max_length }) => {
|
|
335
|
+
try {
|
|
336
|
+
const result = await secFilingsAdapter({ url, maxLength: max_length });
|
|
337
|
+
const ctx = stampFreshness(result, { url, maxLength: max_length }, "sec_filings");
|
|
338
|
+
return { content: [{ type: "text", text: formatForLLM(ctx) }] };
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
return { content: [{ type: "text", text: formatSecurityError(err) }] };
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
// ─── Tool: extract_gdelt ─────────────────────────────────────────────────────
|
|
345
|
+
// GDELT monitors news from every country in 100+ languages, updated every 15 min.
|
|
346
|
+
// Returns structured global news intelligence — not just headlines but event
|
|
347
|
+
// codes, actor tags, tone scores, goldstein scale (impact), location, timestamp.
|
|
348
|
+
// Unique: no other MCP server has this.
|
|
349
|
+
server.registerTool("extract_gdelt", {
|
|
350
|
+
description: "Fetch global news intelligence from the GDELT Project. GDELT monitors broadcast, print, and web news from every country in 100+ languages, updated every 15 minutes. Returns structured articles with source country, language, and publication date. Free, no auth. Pass any company name, topic, or keyword. Unique: not available in any other MCP server.",
|
|
351
|
+
inputSchema: z.object({
|
|
352
|
+
url: z.string().describe("Query: company name, topic, or keyword e.g. 'Palantir', 'artificial intelligence', 'MCP server'"),
|
|
353
|
+
max_length: z.number().optional().default(6000),
|
|
354
|
+
}),
|
|
355
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
356
|
+
}, async ({ url, max_length }) => {
|
|
357
|
+
try {
|
|
358
|
+
const result = await gdeltAdapter({ url, maxLength: max_length });
|
|
359
|
+
const ctx = stampFreshness(result, { url, maxLength: max_length }, "gdelt");
|
|
360
|
+
return { content: [{ type: "text", text: formatForLLM(ctx) }] };
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
return { content: [{ type: "text", text: formatSecurityError(err) }] };
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
// ─── Tool: extract_company_landscape ─────────────────────────────────────────
|
|
367
|
+
// The most complete single-call company intelligence available in any MCP server.
|
|
368
|
+
// 5 moat sources: SEC 8-K legal disclosures + federal contract footprint +
|
|
369
|
+
// global news intelligence + product release velocity + market pricing.
|
|
370
|
+
// Unique: this combination exists nowhere else.
|
|
371
|
+
server.registerTool("extract_company_landscape", {
|
|
372
|
+
description: "Composite company intelligence tool. The most complete single-call company analysis available. Simultaneously queries 5 unique sources: (1) SEC EDGAR for 8-K material event filings — what the company legally just disclosed, (2) USASpending.gov for federal contract footprint — who is giving them government money, (3) GDELT for global news intelligence — what the world is saying about them right now, (4) their product changelog — are they actually shipping, (5) Yahoo Finance — what the market is pricing in. Returns a unified 5-source timestamped report. Unique: this combination is not available in any other MCP server.",
|
|
373
|
+
inputSchema: z.object({
|
|
374
|
+
company: z.string().describe("Company name e.g. 'Palantir', 'Anthropic', 'OpenAI'"),
|
|
375
|
+
ticker: z.string().optional().describe("Stock ticker for finance data e.g. 'PLTR'. Leave blank for private companies."),
|
|
376
|
+
github_url: z.string().optional().describe("Optional GitHub repo or org URL e.g. 'https://github.com/palantir'. Improves changelog accuracy."),
|
|
377
|
+
max_length: z.number().optional().default(15000),
|
|
378
|
+
}),
|
|
379
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
380
|
+
}, async ({ company, ticker, github_url, max_length }) => {
|
|
381
|
+
const perSection = Math.floor((max_length ?? 15000) / 5);
|
|
382
|
+
const repoQuery = github_url ?? company;
|
|
383
|
+
const [secResult, contractsResult, gdeltResult, changelogResult, financeResult] = await Promise.allSettled([
|
|
384
|
+
// 1. What did they legally just disclose
|
|
385
|
+
secFilingsAdapter({ url: company, maxLength: perSection }),
|
|
386
|
+
// 2. Who is giving them government money
|
|
387
|
+
govContractsAdapter({ url: company, maxLength: perSection }),
|
|
388
|
+
// 3. What is global news saying right now
|
|
389
|
+
gdeltAdapter({ url: company, maxLength: perSection }),
|
|
390
|
+
// 4. Are they actually shipping product
|
|
391
|
+
changelogAdapter({ url: repoQuery, maxLength: perSection }),
|
|
392
|
+
// 5. What is the market pricing in
|
|
393
|
+
financeAdapter({ url: ticker ?? company, maxLength: perSection }),
|
|
394
|
+
]);
|
|
395
|
+
const section = (label, result) => result.status === "fulfilled"
|
|
396
|
+
? `## ${label}\n${result.value.raw}`
|
|
397
|
+
: `## ${label}\n[Unavailable: ${result.reason}]`;
|
|
398
|
+
const combined = [
|
|
399
|
+
`# Company Intelligence Landscape: "${company}"${ticker ? ` (${ticker})` : ""}`,
|
|
400
|
+
`Generated: ${new Date().toISOString()}`,
|
|
401
|
+
`Sources: SEC EDGAR · USASpending.gov · GDELT · Changelog · Yahoo Finance`,
|
|
402
|
+
"",
|
|
403
|
+
section("📋 SEC 8-K Filings — Legal Disclosures", secResult),
|
|
404
|
+
section("🏛️ Federal Contract Awards (USASpending.gov)", contractsResult),
|
|
405
|
+
section("🌍 Global News Intelligence (GDELT)", gdeltResult),
|
|
406
|
+
section("🔄 Product Release Velocity (Changelog)", changelogResult),
|
|
407
|
+
section("📈 Market Data (Yahoo Finance)", financeResult),
|
|
408
|
+
].join("\n\n");
|
|
409
|
+
return { content: [{ type: "text", text: combined }] };
|
|
410
|
+
});
|
|
320
411
|
// ─── Start ───────────────────────────────────────────────────────────────────
|
|
321
412
|
async function main() {
|
|
322
413
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED