crawlforge-mcp-server 5.5.5 → 5.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crawlforge-mcp-server",
3
- "version": "5.5.5",
3
+ "version": "5.5.6",
4
4
  "mcpName": "io.github.mysleekdesigns/crawlforge-mcp-server",
5
5
  "description": "CrawlForge MCP Server - Professional Model Context Protocol server with 29 web scraping, crawling, deep-research, and autonomous-extraction tools. Returns clean Markdown and structured JSON for Claude, Cursor, and any MCP client. Defaults to local Ollama for LLM extraction (no API key needed); OpenAI/Anthropic available as opt-in. Includes a unified multi-format scrape tool, an autonomous agent, pre-built site templates, and Camoufox stealth browsing.",
6
6
  "main": "server.js",
@@ -114,7 +114,7 @@
114
114
  "cheerio": "^1.1.2",
115
115
  "commander": "^14.0.3",
116
116
  "compromise": "^14.14.4",
117
- "crawlforge-extractors": "^1.5.2",
117
+ "crawlforge-extractors": "^1.5.3",
118
118
  "diff": "^9.0.0",
119
119
  "dotenv": "^17.2.1",
120
120
  "franc": "^6.2.0",
package/server.js CHANGED
@@ -105,7 +105,7 @@ const taskStore = createTaskStore({ logger });
105
105
  // Create the server
106
106
  const server = new McpServer({
107
107
  name: "crawlforge",
108
- version: "5.5.5",
108
+ version: "5.5.6",
109
109
  description: "Production-ready MCP server with 29 web scraping, crawling, and content processing tools. Features MCP Resources (crawlforge://), Prompts, Sampling fallback, Elicitation, stealth browsing, deep research, structured extraction, embedded JavaScript state extraction, real Google SERP rank tracking, Reddit search via community archives, change tracking, local-LLM extraction via Ollama, unified multi-format scrape, and autonomous agent tool.",
110
110
  homepage: "https://www.crawlforge.dev",
111
111
  icon: "https://www.crawlforge.dev/icon.png",
package/setup.js CHANGED
@@ -68,8 +68,11 @@ function addToMcpConfig(configPath, clientName, apiKey) {
68
68
  }
69
69
  };
70
70
 
71
- // Write updated config
72
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
71
+ // Write updated config. This file now embeds the API key, so keep it
72
+ // owner-only (0o600). The `mode` option only applies when writeFileSync
73
+ // creates the file, so chmod enforces it for a pre-existing config too.
74
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 });
75
+ fs.chmodSync(configPath, 0o600);
73
76
 
74
77
  return {
75
78
  success: true,
@@ -107,7 +110,7 @@ function configureMcpClients(apiKey) {
107
110
  if (!fs.existsSync(cursorConfigPath)) {
108
111
  // Create mcp.json if it doesn't exist
109
112
  try {
110
- fs.writeFileSync(cursorConfigPath, JSON.stringify({ mcpServers: {} }, null, 2));
113
+ fs.writeFileSync(cursorConfigPath, JSON.stringify({ mcpServers: {} }, null, 2), { mode: 0o600 });
111
114
  } catch (e) {
112
115
  // Ignore creation errors
113
116
  }
@@ -10,8 +10,21 @@
10
10
  import crypto from 'crypto';
11
11
  import dotenv from 'dotenv';
12
12
 
13
- // Load .env file early to check for creator secret
14
- dotenv.config({ path: '.env', quiet: true });
13
+ // Load the cwd .env early to check for the creator secret, but into an isolated
14
+ // object rather than straight into process.env. A .env sitting in whatever
15
+ // directory the server is launched from is untrusted; seeding process.env from
16
+ // it directly would let it silently flip the SSRF controls (this module is
17
+ // imported before config.js runs its package-relative .env load). We copy the
18
+ // benign keys through — preserving the historical "cwd .env is read" behavior,
19
+ // including the creator secret — but never the SSRF/allowlist controls, which
20
+ // must come from the real environment.
21
+ const CWD_ENV_DENYLIST = new Set(['SSRF_PROTECTION_ENABLED', 'SSRF_STRICT', 'ALLOWED_DOMAINS']);
22
+ const cwdEnv = {};
23
+ dotenv.config({ path: '.env', quiet: true, processEnv: cwdEnv });
24
+ for (const [key, value] of Object.entries(cwdEnv)) {
25
+ if (CWD_ENV_DENYLIST.has(key)) continue;
26
+ if (process.env[key] === undefined) process.env[key] = value; // don't override the real env
27
+ }
15
28
 
16
29
  // SECURITY: Clear any externally-set creator mode env var to prevent bypass
17
30
  delete process.env.CRAWLFORGE_CREATOR_MODE;
@@ -151,7 +151,14 @@ function sendRpcError(res, status, code, message) {
151
151
  */
152
152
  export async function connectStreamableHttp(server, authManager, logger, options = {}) {
153
153
  const port = options.port ?? 3000;
154
- const host = options.host ?? '0.0.0.0';
154
+ // Bind loopback by default so a local `--http` server is not exposed to the
155
+ // LAN. Managed hosts need 0.0.0.0 to be reachable by their router: Render sets
156
+ // $RENDER; $MCP_HTTP_HOST is an explicit override for any other platform.
157
+ const host = options.host ?? process.env.MCP_HTTP_HOST ?? (process.env.RENDER ? '0.0.0.0' : '127.0.0.1');
158
+ const hostIsLoopback = host === '127.0.0.1' || host === '::1' || host === 'localhost';
159
+ if (authManager.isCreatorMode() && !hostIsLoopback) {
160
+ console.error(`WARNING: creator mode is enabled but the server is bound to ${host} (non-loopback) — per-request auth will NOT be bypassed. Bind to 127.0.0.1 to use creator mode.`);
161
+ }
155
162
  const legacy = options.legacy === true;
156
163
  const oauthProvider = options.oauth ?? null;
157
164
  const metrics = options.metrics ?? null;
@@ -242,12 +249,14 @@ export async function connectStreamableHttp(server, authManager, logger, options
242
249
 
243
250
  // MCP endpoint
244
251
  if (req.url === '/mcp' || req.url === '/' || req.url?.startsWith('/mcp?')) {
245
- // Per-request auth (bypassed in creator mode). `internal` marks a
246
- // request from the website's REST proxy (INTERNAL_PROXY_SECRET): it is
247
- // billing-exempt in withAuth because the website already charged the
248
- // end user. Request-scoped only — never persisted on the session.
252
+ // Per-request auth. Creator mode bypasses it, but only on a loopback bind
253
+ // — never expose an unauthenticated MCP endpoint on a public interface.
254
+ // `internal` marks a request from the website's REST proxy
255
+ // (INTERNAL_PROXY_SECRET): it is billing-exempt in withAuth because the
256
+ // website already charged the end user. Request-scoped only — never
257
+ // persisted on the session.
249
258
  let internal = false;
250
- if (!authManager.isCreatorMode()) {
259
+ if (!(authManager.isCreatorMode() && hostIsLoopback)) {
251
260
  const authResult = await authenticateRequest(req, authManager, oauthProvider);
252
261
  if (!authResult.ok) {
253
262
  logger.warn('Streamable HTTP request rejected', {