mcp-camoufox 0.5.1 → 0.5.3

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.
Files changed (3) hide show
  1. package/README.md +35 -6
  2. package/dist/index.js +93 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  </div>
13
13
 
14
- The most feature-rich stealth browser MCP server. **79 tools** for full browser control powered by [Camoufox](https://github.com/daijro/camoufox) — a Firefox fork with C++ level anti-detection that bypasses Cloudflare, bot detection, and anti-automation.
14
+ The most feature-rich stealth browser MCP server. **80 tools** for full browser control powered by [Camoufox](https://github.com/daijro/camoufox) — a Firefox fork with C++ level anti-detection that bypasses Cloudflare, bot detection, and anti-automation.
15
15
 
16
16
  > **One command. No Python. No manual setup. Everything auto-installs.**
17
17
 
@@ -39,7 +39,35 @@ claude mcp add camoufox -- npx -y mcp-camoufox@latest
39
39
  | redf0x1/camofox-mcp | 45 | Yes | No (clone) | Yes |
40
40
  | Sekinal/camoufox-mcp | 49 | Yes | No (clone) | Yes |
41
41
  | Playwright CLI | 60+ | No | Yes | Yes |
42
- | **[mcp-camoufox](https://github.com/RobithYusuf/mcp-camoufox)** | **79** | **Yes** | **Yes** | **Yes** |
42
+ | **[mcp-camoufox](https://github.com/RobithYusuf/mcp-camoufox)** | **80** | **Yes** | **Yes** | **Yes** |
43
+
44
+ ## Proven on Real Sites
45
+
46
+ | Site | Challenge | Result |
47
+ |------|-----------|--------|
48
+ | `2captcha.com/demo/cloudflare-turnstile` | Cloudflare Turnstile widget | ✅ **"Success!"** via `click_turnstile()` tool ([proof](docs/images/turnstile.jpg)) |
49
+ | `bot.sannysoft.com` | Firefox fingerprint tests | ✅ All green ([proof](docs/images/sannysoft.jpg)) |
50
+ | `browserscan.net/bot-detection` | WebDriver/UA/CDP/Navigator | ✅ All categories "Normal" ([proof](docs/images/browserscan.jpg)) |
51
+
52
+ ### 🎯 Cloudflare Turnstile → Success via `click_turnstile()`
53
+
54
+ <img src="docs/images/turnstile.jpg" alt="Cloudflare Turnstile success" width="500">
55
+
56
+ `click_turnstile()` auto-detects the widget via 6 selector fallback (`iframe[src*=challenges.cloudflare.com]`, `[data-sitekey]`, `.cf-turnstile`, …), computes checkbox position (offset_x=30 from widget left), and clicks with a 3-step Bezier-like approach — combined with Camoufox's native `humanize` + `disable_coop` for cross-origin iframe click.
57
+
58
+ **Scope:** works on **Interactive Turnstile** (visible iframe widget). **Managed Challenge** interstitials ("Just a moment...") render the widget in shadow DOM — not supported here; use sister project [mcp-stealth-chrome](https://github.com/RobithYusuf/mcp-stealth-chrome) (Chrome+CDP) for those. Real-world bypass success also depends on IP reputation and browser fingerprint — code alone doesn't guarantee it.
59
+
60
+ ### 🧪 bot.sannysoft.com → Firefox Fingerprint Pass
61
+
62
+ <img src="docs/images/sannysoft.jpg" alt="sannysoft Firefox pass" width="500">
63
+
64
+ User Agent reports `Firefox/135.0`, WebDriver missing, WebDriver Advanced passed, Permissions prompt, Plugins length 5 passed, Languages `en-US,en`, WebGL Intel HD Graphics — all green. ("Chrome: missing" is expected — Camoufox spoofs Firefox, not Chrome.)
65
+
66
+ ### 🔍 browserscan.net/bot-detection → All Categories Normal
67
+
68
+ <img src="docs/images/browserscan.jpg" alt="browserscan normal" width="500">
69
+
70
+ WebDriver, User-Agent, CDP, Navigator — every detection category returns **"Normal"**. Camoufox's C++-level Firefox patches leave zero automation signals.
43
71
 
44
72
  ## Setup
45
73
 
@@ -234,7 +262,7 @@ Or via UI: Agent Panel > `...` > MCP Servers > Manage MCP Servers > View raw con
234
262
 
235
263
  That's all. Camoufox browser binary (~80MB) downloads automatically on first launch.
236
264
 
237
- ## All 79 Tools
265
+ ## All 80 Tools
238
266
 
239
267
  ### Browser Lifecycle (2)
240
268
 
@@ -283,12 +311,13 @@ That's all. Camoufox browser binary (~80MB) downloads automatically on first lau
283
311
  | `type_text` | Type char by char. Options: `delay`. For OTP, masked inputs, date pickers. |
284
312
  | `press_key` | Key or combo: `Enter`, `Escape`, `Tab`, `Control+a`, `Meta+c` |
285
313
 
286
- ### Mouse XY (3)
314
+ ### Mouse XY (4)
287
315
 
288
316
  | Tool | Description |
289
317
  |------|-------------|
290
- | `mouse_click_xy` | Click at exact coordinates |
291
- | `mouse_move` | Move cursor to coordinates |
318
+ | `mouse_click_xy` | Click at exact coordinates. Optional `steps` (0=instant, 15-30=human-like pre-movement) |
319
+ | `mouse_move` | Move cursor to coordinates. Optional `steps` for interpolated path |
320
+ | `click_turnstile` | Auto-find + humanized click on Cloudflare Turnstile widget. Params: `offset_x` (default 30), `offset_y`, `wait_render_ms`. Works on Interactive Turnstile (visible iframe widget). Not for Managed Challenge interstitials. |
292
321
  | `drag_and_drop` | Drag between two elements |
293
322
 
294
323
  ### Wait (4)
package/dist/index.js CHANGED
@@ -113,6 +113,37 @@ const server = new McpServer({
113
113
  version: "0.2.0",
114
114
  });
115
115
  // ── Tools: Browser Lifecycle ───────────────────────────────────────────────
116
+ async function ensureCamoufoxBinary() {
117
+ const { execSync } = await import("child_process");
118
+ const { existsSync } = await import("fs");
119
+ const { join: pathJoin } = await import("path");
120
+ const os = await import("os");
121
+ const homeDir = os.homedir();
122
+ const platform = os.platform();
123
+ let cacheDir;
124
+ if (platform === "darwin") {
125
+ cacheDir = pathJoin(homeDir, "Library", "Caches", "camoufox");
126
+ }
127
+ else if (platform === "win32") {
128
+ cacheDir = pathJoin(process.env.LOCALAPPDATA || pathJoin(homeDir, "AppData", "Local"), "camoufox");
129
+ }
130
+ else {
131
+ cacheDir = pathJoin(process.env.XDG_CACHE_HOME || pathJoin(homeDir, ".cache"), "camoufox");
132
+ }
133
+ const versionFile = pathJoin(cacheDir, "version.json");
134
+ if (existsSync(versionFile))
135
+ return;
136
+ console.error("\n" + "=".repeat(60));
137
+ console.error("[mcp-camoufox] First-time setup: downloading Camoufox (~500MB)");
138
+ console.error("[mcp-camoufox] Please wait 2-5 minutes...");
139
+ console.error("=".repeat(60) + "\n");
140
+ const cmd = platform === "win32" ? "npx.cmd" : "npx";
141
+ execSync(`${cmd} camoufox-js fetch`, {
142
+ stdio: "inherit", timeout: 900000,
143
+ env: { ...process.env, npm_config_yes: "true" },
144
+ });
145
+ console.error("\n[mcp-camoufox] Download complete.\n");
146
+ }
116
147
  server.tool("browser_launch", "Launch Camoufox stealth browser and navigate to URL. Browser persists between calls. Call this first.", {
117
148
  url: z.string().default("about:blank").describe("URL to navigate to"),
118
149
  headless: z.boolean().default(true).describe("Run without visible window"),
@@ -133,57 +164,7 @@ server.tool("browser_launch", "Launch Camoufox stealth browser and navigate to U
133
164
  ensureDirs();
134
165
  const w = width > 0 ? width : 1280;
135
166
  const h = height > 0 ? height : 800;
136
- // Auto-download Camoufox binary if not installed
137
- // camoufox-js does NOT auto-download — we handle it here
138
- await (async () => {
139
- const { execSync } = await import("child_process");
140
- const { existsSync, readdirSync } = await import("fs");
141
- const { join: pathJoin } = await import("path");
142
- const os = await import("os");
143
- // Detect cache dir per platform (same logic as camoufox-js pkgman.ts)
144
- const homeDir = os.homedir();
145
- const platform = os.platform();
146
- let cacheDir;
147
- if (platform === "darwin") {
148
- cacheDir = pathJoin(homeDir, "Library", "Caches", "camoufox");
149
- }
150
- else if (platform === "win32") {
151
- cacheDir = pathJoin(process.env.LOCALAPPDATA || pathJoin(homeDir, "AppData", "Local"), "camoufox");
152
- }
153
- else {
154
- cacheDir = pathJoin(process.env.XDG_CACHE_HOME || pathJoin(homeDir, ".cache"), "camoufox");
155
- }
156
- // Check if binary exists (look for version.json inside cache dir)
157
- const versionFile = pathJoin(cacheDir, "version.json");
158
- const isInstalled = existsSync(versionFile);
159
- if (!isInstalled) {
160
- console.error("");
161
- console.error("=".repeat(60));
162
- console.error("[mcp-camoufox] First-time setup: downloading Camoufox browser");
163
- console.error("[mcp-camoufox] This is ~500MB and only happens once.");
164
- console.error("[mcp-camoufox] Please wait 2-5 minutes...");
165
- console.error("=".repeat(60));
166
- console.error("");
167
- try {
168
- // Use npx to run camoufox-js CLI fetch command
169
- const cmd = platform === "win32" ? "npx.cmd" : "npx";
170
- execSync(`${cmd} camoufox-js fetch`, {
171
- stdio: "inherit",
172
- timeout: 900000, // 15 min max
173
- env: { ...process.env, npm_config_yes: "true" },
174
- });
175
- console.error("");
176
- console.error("[mcp-camoufox] Download complete! Browser ready.");
177
- console.error("");
178
- }
179
- catch (fetchErr) {
180
- console.error(`[mcp-camoufox] Auto-download failed: ${fetchErr.message?.slice(0, 200)}`);
181
- console.error("[mcp-camoufox] Try manually: npx camoufox-js fetch");
182
- throw new Error("Camoufox browser binary not found. Auto-download failed. " +
183
- "Please run manually: npx camoufox-js fetch");
184
- }
185
- }
186
- })();
167
+ await ensureCamoufoxBinary();
187
168
  const ctx = await Camoufox({
188
169
  headless,
189
170
  humanize,
@@ -823,21 +804,74 @@ server.tool("sessionstorage_set", "Set a sessionStorage item.", {
823
804
  return { content: [{ type: "text", text: `sessionStorage set: ${key}` }] };
824
805
  });
825
806
  // ── Tools: Mouse XY ────────────────────────────────────────────────────────
826
- server.tool("mouse_click_xy", "Click at exact x,y coordinates on the page.", {
807
+ server.tool("mouse_click_xy", "Click at exact x,y coordinates. steps>0 adds interpolated pre-movement (human-like).", {
827
808
  x: z.number(), y: z.number(),
828
809
  button: z.enum(["left", "right", "middle"]).default("left"),
829
- }, async ({ x, y, button }) => {
810
+ steps: z.number().default(0).describe("Interpolation steps for pre-click movement (0=instant, 15-30=human-like)"),
811
+ }, async ({ x, y, button, steps }) => {
830
812
  const page = getPage();
813
+ if (steps > 0) {
814
+ await page.mouse.move(x, y, { steps });
815
+ await page.waitForTimeout(80 + Math.random() * 60);
816
+ }
831
817
  await page.mouse.click(x, y, { button });
832
818
  await page.waitForTimeout(500);
833
- return { content: [{ type: "text", text: `Clicked at (${x}, ${y}) button=${button}` }] };
819
+ return { content: [{ type: "text", text: `Clicked at (${x}, ${y}) button=${button} steps=${steps}` }] };
834
820
  });
835
- server.tool("mouse_move", "Move mouse to exact x,y coordinates.", {
821
+ server.tool("mouse_move", "Move mouse to x,y. steps>0 interpolates path (human-like).", {
836
822
  x: z.number(), y: z.number(),
837
- }, async ({ x, y }) => {
838
- const page = getPage();
839
- await page.mouse.move(x, y);
840
- return { content: [{ type: "text", text: `Mouse moved to (${x}, ${y})` }] };
823
+ steps: z.number().default(0).describe("Interpolation steps (0=instant jump, 15-30=smooth)"),
824
+ }, async ({ x, y, steps }) => {
825
+ const page = getPage();
826
+ await page.mouse.move(x, y, steps > 0 ? { steps } : undefined);
827
+ return { content: [{ type: "text", text: `Mouse moved to (${x}, ${y}) steps=${steps}` }] };
828
+ });
829
+ server.tool("click_turnstile", "Auto-find and click Cloudflare Turnstile checkbox. Port of mcp-stealth-chrome's proven pattern — single pre-drift + direct click, leaning on Camoufox's built-in humanize + disable_coop for cross-origin iframe support. Works on Interactive Turnstile widgets (visible iframe). Managed Challenge interstitials not supported — use mcp-stealth-chrome for those.", {
830
+ offset_x: z.number().default(30).describe("Pixels from widget left edge (calibrated for CF checkbox)"),
831
+ offset_y: z.number().optional().describe("Vertical offset from widget top (default = height/2)"),
832
+ wait_render_ms: z.number().default(500).describe("Wait before detection to let widget render"),
833
+ }, async ({ offset_x, offset_y, wait_render_ms }) => {
834
+ const page = getPage();
835
+ await page.waitForTimeout(wait_render_ms);
836
+ // Widget detection — 6 selectors ordered by specificity (port from mcp-stealth-chrome)
837
+ const coords = await page.evaluate(() => {
838
+ const sels = [
839
+ 'iframe[src*="challenges.cloudflare.com"]',
840
+ 'iframe[src*="turnstile"]',
841
+ '[data-testid*="challenge-widget"]',
842
+ '[data-testid*="turnstile"]',
843
+ '[data-sitekey]',
844
+ '.cf-turnstile',
845
+ ];
846
+ for (const sel of sels) {
847
+ const el = document.querySelector(sel);
848
+ if (!el)
849
+ continue;
850
+ const r = el.getBoundingClientRect();
851
+ if (r.width < 50 || r.height < 20)
852
+ continue;
853
+ return {
854
+ found: sel,
855
+ left: Math.round(r.left),
856
+ top: Math.round(r.top),
857
+ width: Math.round(r.width),
858
+ height: Math.round(r.height),
859
+ };
860
+ }
861
+ return null;
862
+ });
863
+ if (!coords) {
864
+ return { content: [{ type: "text", text: "Turnstile widget not found — selector miss. Likely a Managed Challenge interstitial (use mcp-stealth-chrome) or widget hasn't rendered yet (try wait_render_ms=3000)." }] };
865
+ }
866
+ const targetX = coords.left + offset_x;
867
+ const targetY = coords.top + (offset_y ?? Math.floor(coords.height / 2));
868
+ // Single pre-drift then direct click (matches stealth-chrome's pattern).
869
+ // Camoufox's humanize layer handles path curvature + timing automatically,
870
+ // so extra Bezier hops would be redundant and slow (~3s extra).
871
+ await page.mouse.move(targetX + 180, targetY - 80, { steps: 15 });
872
+ await page.waitForTimeout(150);
873
+ await page.mouse.click(targetX, targetY);
874
+ return { content: [{ type: "text", text: `clicked Turnstile at (${targetX},${targetY}) — widget found via ${coords.found} (${coords.width}x${coords.height})` }] };
841
875
  });
842
876
  server.tool("drag_and_drop", "Drag from one element to another.", {
843
877
  source_ref: z.string().describe("Ref of element to drag"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-camoufox",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "MCP server for stealth browser automation via Camoufox — 79 tools, Chrome DevTools MCP-level power with anti-bot stealth",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",