freshcontext-mcp 0.1.4 → 0.1.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/README.md CHANGED
@@ -21,14 +21,14 @@ Every piece of data extracted by `freshcontext-mcp` is wrapped in a structured e
21
21
  [FRESHCONTEXT]
22
22
  Source: https://github.com/owner/repo
23
23
  Published: 2024-11-03
24
- Retrieved: 2026-03-03T10:14:00Z
24
+ Retrieved: 2026-03-04T10:14:00Z
25
25
  Confidence: high
26
26
  ---
27
27
  ... content ...
28
28
  [/FRESHCONTEXT]
29
29
  ```
30
30
 
31
- The AI agent always knows **when it's looking at data**, not just what the data says. This is the difference between a hallucinated recency claim and a verifiable one.
31
+ The AI agent always knows **when it's looking at data**, not just what the data says.
32
32
 
33
33
  ---
34
34
 
@@ -60,13 +60,33 @@ The AI agent always knows **when it's looking at data**, not just what the data
60
60
 
61
61
  ## Quick Start
62
62
 
63
- ### Install via npm
63
+ ### Option A — Cloud (no install, works immediately)
64
64
 
65
- ```bash
66
- npx freshcontext-mcp
65
+ No Node, no Playwright, nothing to install. Just add this to your Claude Desktop config and restart.
66
+
67
+ **Mac:** open `~/Library/Application Support/Claude/claude_desktop_config.json`
68
+ **Windows:** open `%APPDATA%\Claude\claude_desktop_config.json`
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "freshcontext": {
74
+ "command": "npx",
75
+ "args": ["-y", "mcp-remote", "https://freshcontext-mcp.gimmanuel73.workers.dev/mcp"]
76
+ }
77
+ }
78
+ }
67
79
  ```
68
80
 
69
- ### Or clone and run locally
81
+ Restart Claude Desktop. The freshcontext tools will appear in your session.
82
+
83
+ > **Note:** If `claude_desktop_config.json` doesn't exist yet, create it with the content above.
84
+
85
+ ---
86
+
87
+ ### Option B — Local (full Playwright, faster for heavy use)
88
+
89
+ **Prerequisites:** Node.js 18+ ([nodejs.org](https://nodejs.org))
70
90
 
71
91
  ```bash
72
92
  git clone https://github.com/PrinceGabriel-lgtm/freshcontext-mcp
@@ -76,39 +96,56 @@ npx playwright install chromium
76
96
  npm run build
77
97
  ```
78
98
 
79
- ### Connect to Claude Desktop
80
-
81
- Add to your `claude_desktop_config.json`:
82
-
83
- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json`
84
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
99
+ Then add to your Claude Desktop config:
85
100
 
101
+ **Mac** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
86
102
  ```json
87
103
  {
88
104
  "mcpServers": {
89
- "freshcontext-local": {
105
+ "freshcontext": {
90
106
  "command": "node",
91
- "args": ["/absolute/path/to/freshcontext-mcp/dist/server.js"]
107
+ "args": ["/Users/YOUR_USERNAME/path/to/freshcontext-mcp/dist/server.js"]
92
108
  }
93
109
  }
94
110
  }
95
111
  ```
96
112
 
97
- Restart Claude Desktop. You'll see the freshcontext tools available in your session.
98
-
99
- ### Or use the Cloudflare edge deployment (no install needed)
100
-
113
+ **Windows** (`%APPDATA%\Claude\claude_desktop_config.json`):
101
114
  ```json
102
115
  {
103
116
  "mcpServers": {
104
- "freshcontext-cloud": {
105
- "command": "npx",
106
- "args": ["-y", "mcp-remote", "https://freshcontext-worker.gimmanuel73.workers.dev/mcp"]
117
+ "freshcontext": {
118
+ "command": "node",
119
+ "args": ["C:\\Users\\YOUR_USERNAME\\path\\to\\freshcontext-mcp\\dist\\server.js"]
107
120
  }
108
121
  }
109
122
  }
110
123
  ```
111
124
 
125
+ Restart Claude Desktop.
126
+
127
+ ---
128
+
129
+ ### Troubleshooting (Mac)
130
+
131
+ **"command not found: node"** — Node isn't on your PATH inside Claude Desktop's environment. Use the full path:
132
+ ```bash
133
+ which node # copy this output
134
+ ```
135
+ Then replace `"command": "node"` with `"command": "/usr/local/bin/node"` (or whatever `which node` returned).
136
+
137
+ **"npx: command not found"** — Same issue. Run `which npx` and use the full path for Option A:
138
+ ```json
139
+ "command": "/usr/local/bin/npx"
140
+ ```
141
+
142
+ **Config file doesn't exist** — Create it. On Mac:
143
+ ```bash
144
+ mkdir -p ~/Library/Application\ Support/Claude
145
+ touch ~/Library/Application\ Support/Claude/claude_desktop_config.json
146
+ ```
147
+ Then paste the config JSON above into it.
148
+
112
149
  ---
113
150
 
114
151
  ## Usage Examples
@@ -162,12 +199,12 @@ This makes freshness **verifiable**, not assumed.
162
199
  Uses headless Chromium via Playwright. Full browser rendering for JavaScript-heavy sites.
163
200
 
164
201
  ### Cloud (Cloudflare Workers)
165
- The `worker/` directory contains a Cloudflare Workers deployment using the Browser Rendering REST API. No Playwright dependency — runs at the edge globally.
202
+ The `worker/` directory contains a Cloudflare Workers deployment. No Playwright dependency — runs at the edge globally.
166
203
 
167
204
  ```bash
168
205
  cd worker
169
206
  npm install
170
- npx wrangler secret put CF_API_TOKEN
207
+ npx wrangler secret put API_KEY
171
208
  npx wrangler deploy
172
209
  ```
173
210
 
@@ -180,15 +217,16 @@ freshcontext-mcp/
180
217
  ├── src/
181
218
  │ ├── server.ts # MCP server, all tool registrations
182
219
  │ ├── types.ts # FreshContext interfaces
220
+ │ ├── security.ts # Input validation, domain allowlists
183
221
  │ ├── adapters/
184
- │ │ ├── github.ts # GitHub repo extraction
185
- │ │ ├── hackernews.ts # HN front page + Algolia API
186
- │ │ ├── scholar.ts # Google Scholar scraping
187
- │ │ ├── yc.ts # YC company directory
188
- │ │ ├── repoSearch.ts # GitHub Search API
189
- │ │ └── packageTrends.ts # npm + PyPI registries
222
+ │ │ ├── github.ts
223
+ │ │ ├── hackernews.ts
224
+ │ │ ├── scholar.ts
225
+ │ │ ├── yc.ts
226
+ │ │ ├── repoSearch.ts
227
+ │ │ └── packageTrends.ts
190
228
  │ └── tools/
191
- │ └── freshnessStamp.ts # FreshContext envelope builder
229
+ │ └── freshnessStamp.ts
192
230
  └── worker/ # Cloudflare Workers deployment
193
231
  └── src/worker.ts
194
232
  ```
@@ -205,17 +243,17 @@ freshcontext-mcp/
205
243
  - [x] npm/PyPI package trends
206
244
  - [x] `extract_landscape` composite tool
207
245
  - [x] Cloudflare Workers deployment
246
+ - [x] Worker auth + rate limiting + domain allowlists
208
247
  - [ ] Product Hunt launches adapter
209
- - [ ] Crunchbase/funding signals adapter
248
+ - [ ] Finance/market data adapter
210
249
  - [ ] TTL-based caching layer
211
250
  - [ ] `freshness_score` numeric metric
212
- - [ ] Webhook support for real-time updates
213
251
 
214
252
  ---
215
253
 
216
254
  ## Contributing
217
255
 
218
- PRs welcome. New adapters are the highest-value contribution — see the existing adapters in `src/adapters/` for the pattern. Each adapter returns `{ raw, content_date, freshness_confidence }`.
256
+ PRs welcome. New adapters are the highest-value contribution — see `src/adapters/` for the pattern. Each adapter returns `{ raw, content_date, freshness_confidence }`.
219
257
 
220
258
  ---
221
259
 
package/dist/server.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
4
  import { z } from "zod";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freshcontext-mcp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Real-time web extraction MCP server with freshness timestamps for AI agents",
5
5
  "keywords": [
6
6
  "mcp",
@@ -24,6 +24,9 @@
24
24
  "license": "MIT",
25
25
  "type": "module",
26
26
  "main": "dist/server.js",
27
+ "bin": {
28
+ "freshcontext-mcp": "dist/server.js"
29
+ },
27
30
  "scripts": {
28
31
  "build": "tsc",
29
32
  "dev": "tsx watch src/server.ts",
package/src/server.ts CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
4
  import { z } from "zod";
@@ -202,3 +203,4 @@ async function main() {
202
203
  }
203
204
 
204
205
  main().catch(console.error);
206
+
@@ -0,0 +1,204 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { githubAdapter } from "./adapters/github.js";
5
+ import { scholarAdapter } from "./adapters/scholar.js";
6
+ import { hackerNewsAdapter } from "./adapters/hackernews.js";
7
+ import { ycAdapter } from "./adapters/yc.js";
8
+ import { repoSearchAdapter } from "./adapters/repoSearch.js";
9
+ import { packageTrendsAdapter } from "./adapters/packageTrends.js";
10
+ import { stampFreshness, formatForLLM } from "./tools/freshnessStamp.js";
11
+ import { SecurityError, formatSecurityError } from "./security.js";
12
+
13
+ const server = new McpServer({
14
+ name: "freshcontext-mcp",
15
+ version: "0.1.0",
16
+ });
17
+
18
+ // ─── Tool: extract_github ────────────────────────────────────────────────────
19
+ server.registerTool(
20
+ "extract_github",
21
+ {
22
+ description:
23
+ "Extract real-time data from a GitHub repository — README, stars, forks, language, topics, last commit. Returns timestamped freshcontext.",
24
+ inputSchema: z.object({
25
+ url: z.string().url().describe("Full GitHub repo URL e.g. https://github.com/owner/repo"),
26
+ max_length: z.number().optional().default(6000).describe("Max content length"),
27
+ }),
28
+ annotations: { readOnlyHint: true, openWorldHint: true },
29
+ },
30
+ async ({ url, max_length }) => {
31
+ try {
32
+ const result = await githubAdapter({ url, maxLength: max_length });
33
+ const ctx = stampFreshness(result, { url, maxLength: max_length }, "github");
34
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
35
+ } catch (err) {
36
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
37
+ }
38
+ }
39
+ );
40
+
41
+ // ─── Tool: extract_scholar ───────────────────────────────────────────────────
42
+ server.registerTool(
43
+ "extract_scholar",
44
+ {
45
+ description:
46
+ "Extract research results from a Google Scholar search URL. Returns titles, authors, publication years, and snippets — all timestamped.",
47
+ inputSchema: z.object({
48
+ url: z.string().url().describe("Google Scholar search URL e.g. https://scholar.google.com/scholar?q=..."),
49
+ max_length: z.number().optional().default(6000),
50
+ }),
51
+ annotations: { readOnlyHint: true, openWorldHint: true },
52
+ },
53
+ async ({ url, max_length }) => {
54
+ try {
55
+ const result = await scholarAdapter({ url, maxLength: max_length });
56
+ const ctx = stampFreshness(result, { url, maxLength: max_length }, "google_scholar");
57
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
58
+ } catch (err) {
59
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
60
+ }
61
+ }
62
+ );
63
+
64
+ // ─── Tool: extract_hackernews ────────────────────────────────────────────────
65
+ server.registerTool(
66
+ "extract_hackernews",
67
+ {
68
+ description:
69
+ "Extract top stories or search results from Hacker News. Real-time dev/tech community sentiment with post timestamps.",
70
+ inputSchema: z.object({
71
+ url: z.string().url().describe("HN URL e.g. https://news.ycombinator.com or https://hn.algolia.com/?q=..."),
72
+ max_length: z.number().optional().default(4000),
73
+ }),
74
+ annotations: { readOnlyHint: true, openWorldHint: true },
75
+ },
76
+ async ({ url, max_length }) => {
77
+ try {
78
+ const result = await hackerNewsAdapter({ url, maxLength: max_length });
79
+ const ctx = stampFreshness(result, { url, maxLength: max_length }, "hackernews");
80
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
81
+ } catch (err) {
82
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
83
+ }
84
+ }
85
+ );
86
+
87
+ // ─── Tool: extract_yc ──────────────────────────────────────────────────────────
88
+ server.registerTool(
89
+ "extract_yc",
90
+ {
91
+ description:
92
+ "Scrape YC company listings. Use https://www.ycombinator.com/companies?query=KEYWORD to find startups in a space. Returns name, batch, tags, description per company with freshness timestamp.",
93
+ inputSchema: z.object({
94
+ url: z.string().url().describe("YC companies URL e.g. https://www.ycombinator.com/companies?query=mcp"),
95
+ max_length: z.number().optional().default(6000),
96
+ }),
97
+ annotations: { readOnlyHint: true, openWorldHint: true },
98
+ },
99
+ async ({ url, max_length }) => {
100
+ try {
101
+ const result = await ycAdapter({ url, maxLength: max_length });
102
+ const ctx = stampFreshness(result, { url, maxLength: max_length }, "ycombinator");
103
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
104
+ } catch (err) {
105
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
106
+ }
107
+ }
108
+ );
109
+
110
+ // ─── Tool: search_repos ──────────────────────────────────────────────────────
111
+ server.registerTool(
112
+ "search_repos",
113
+ {
114
+ description:
115
+ "Search GitHub for repositories matching a keyword or topic. Returns top results by stars with activity signals. Use to find competitors, similar tools, or related projects.",
116
+ inputSchema: z.object({
117
+ query: z.string().describe("Search query e.g. 'mcp server typescript' or 'cashflow prediction python'"),
118
+ max_length: z.number().optional().default(6000),
119
+ }),
120
+ annotations: { readOnlyHint: true, openWorldHint: true },
121
+ },
122
+ async ({ query, max_length }) => {
123
+ try {
124
+ const result = await repoSearchAdapter({ url: query, maxLength: max_length });
125
+ const ctx = stampFreshness(result, { url: query, maxLength: max_length }, "github_search");
126
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
127
+ } catch (err) {
128
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
129
+ }
130
+ }
131
+ );
132
+
133
+ // ─── Tool: package_trends ────────────────────────────────────────────────────
134
+ server.registerTool(
135
+ "package_trends",
136
+ {
137
+ description:
138
+ "Look up npm and PyPI package metadata — version history, release cadence, last updated. Use to gauge ecosystem activity around a tool or dependency. Supports comma-separated list of packages.",
139
+ inputSchema: z.object({
140
+ packages: z.string().describe("Package name(s) e.g. 'langchain' or 'npm:zod,pypi:fastapi'"),
141
+ max_length: z.number().optional().default(5000),
142
+ }),
143
+ annotations: { readOnlyHint: true, openWorldHint: true },
144
+ },
145
+ async ({ packages, max_length }) => {
146
+ try {
147
+ const result = await packageTrendsAdapter({ url: packages, maxLength: max_length });
148
+ const ctx = stampFreshness(result, { url: packages, maxLength: max_length }, "package_registry");
149
+ return { content: [{ type: "text", text: formatForLLM(ctx) }] };
150
+ } catch (err) {
151
+ return { content: [{ type: "text", text: formatSecurityError(err) }] };
152
+ }
153
+ }
154
+ );
155
+
156
+ // ─── Tool: extract_landscape ─────────────────────────────────────────────────
157
+ server.registerTool(
158
+ "extract_landscape",
159
+ {
160
+ description:
161
+ "Composite intelligence tool. Given a project idea or keyword, simultaneously queries YC startups, GitHub repos, HN sentiment, and package activity to answer: Who is building this? Is it funded? What's getting traction? Returns a unified timestamped landscape report.",
162
+ inputSchema: z.object({
163
+ topic: z.string().describe("Your project idea or keyword e.g. 'mcp server' or 'cashflow prediction'"),
164
+ max_length: z.number().optional().default(8000),
165
+ }),
166
+ annotations: { readOnlyHint: true, openWorldHint: true },
167
+ },
168
+ async ({ topic, max_length }) => {
169
+ const perSection = Math.floor((max_length ?? 8000) / 4);
170
+
171
+ const [ycResult, repoResult, hnResult, pkgResult] = await Promise.allSettled([
172
+ ycAdapter({ url: `https://www.ycombinator.com/companies?query=${encodeURIComponent(topic)}`, maxLength: perSection }),
173
+ repoSearchAdapter({ url: topic, maxLength: perSection }),
174
+ hackerNewsAdapter({ url: `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(topic)}&tags=story&hitsPerPage=15`, maxLength: perSection }),
175
+ packageTrendsAdapter({ url: topic, maxLength: perSection }),
176
+ ]);
177
+
178
+ const section = (label: string, result: PromiseSettledResult<{ raw: string; content_date: string | null; freshness_confidence: string }>) =>
179
+ result.status === "fulfilled"
180
+ ? `## ${label}\n${result.value.raw}`
181
+ : `## ${label}\n[Error: ${(result as PromiseRejectedResult).reason}]`;
182
+
183
+ const combined = [
184
+ `# Landscape Report: "${topic}"`,
185
+ `Generated: ${new Date().toISOString()}`,
186
+ "",
187
+ section("🚀 YC Startups in this space", ycResult),
188
+ section("📦 Top GitHub repos", repoResult),
189
+ section("💬 HN sentiment (last month)", hnResult),
190
+ section("📊 Package ecosystem", pkgResult),
191
+ ].join("\n\n");
192
+
193
+ return { content: [{ type: "text", text: combined }] };
194
+ }
195
+ );
196
+
197
+ // ─── Start ───────────────────────────────────────────────────────────────────
198
+ async function main() {
199
+ const transport = new StdioServerTransport();
200
+ await server.connect(transport);
201
+ console.error("freshcontext-mcp running on stdio");
202
+ }
203
+
204
+ main().catch(console.error);