mcp-camoufox 0.4.9 → 0.5.1

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 +29 -4
  2. package/dist/index.js +178 -0
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  </div>
13
13
 
14
- The most feature-rich stealth browser MCP server. **69 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. **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.
15
15
 
16
16
  > **One command. No Python. No manual setup. Everything auto-installs.**
17
17
 
@@ -39,7 +39,7 @@ 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)** | **69** | **Yes** | **Yes** | **Yes** |
42
+ | **[mcp-camoufox](https://github.com/RobithYusuf/mcp-camoufox)** | **79** | **Yes** | **Yes** | **Yes** |
43
43
 
44
44
  ## Setup
45
45
 
@@ -234,7 +234,7 @@ Or via UI: Agent Panel > `...` > MCP Servers > Manage MCP Servers > View raw con
234
234
 
235
235
  That's all. Camoufox browser binary (~80MB) downloads automatically on first launch.
236
236
 
237
- ## All 69 Tools
237
+ ## All 79 Tools
238
238
 
239
239
  ### Browser Lifecycle (2)
240
240
 
@@ -395,6 +395,30 @@ That's all. Camoufox browser binary (~80MB) downloads automatically on first lau
395
395
  | `console_start` / `console_get` | Capture and retrieve browser console messages |
396
396
  | `network_start` / `network_get` | Capture and retrieve network requests |
397
397
 
398
+ ### Compound (reduce round-trips) (4)
399
+
400
+ | Tool | Description |
401
+ |------|-------------|
402
+ | `wait_and_snapshot` | Wait for selector/text + return snapshot in one call |
403
+ | `back_and_snapshot` | Navigate back + return snapshot |
404
+ | `reload_and_snapshot` | Reload page + return snapshot |
405
+ | `click_and_snapshot` | Click + wait + return snapshot. Perfect for buttons that trigger navigation. |
406
+
407
+ ### Smart Selectors (skip snapshot) (3)
408
+
409
+ | Tool | Description |
410
+ |------|-------------|
411
+ | `find_by_text` | Find element by visible text, returns ref. Skip `browser_snapshot` when you know exact text. |
412
+ | `find_by_label` | Find input by label text, returns ref. |
413
+ | `find_by_placeholder` | Find input by placeholder, returns ref. |
414
+
415
+ ### Session Portability (2)
416
+
417
+ | Tool | Description |
418
+ |------|-------------|
419
+ | `cookie_export` | Export all cookies as JSON (for transfer) |
420
+ | `cookie_import` | Import cookies from JSON (restore session) |
421
+
398
422
  ### Scraping & Extraction (4)
399
423
 
400
424
  | Tool | Description |
@@ -404,13 +428,14 @@ That's all. Camoufox browser binary (~80MB) downloads automatically on first lau
404
428
  | `extract_table` | Extract HTML table as JSON array with auto-detected headers |
405
429
  | `scrape_page` | Smart scraper: auto-extract main content (strips nav/footer), links, meta, headings. Smart truncation at paragraph boundary. |
406
430
 
407
- ### Debug (3)
431
+ ### Debug (4)
408
432
 
409
433
  | Tool | Description |
410
434
  |------|-------------|
411
435
  | `server_status` | Health check: browser status, tabs, URL |
412
436
  | `get_page_errors` | JS errors from page |
413
437
  | `export_har` | Export network traffic as HAR file |
438
+ | `page_stats` | Element count, page size, load metrics + extraction strategy recommendation |
414
439
 
415
440
  ## Examples
416
441
 
package/dist/index.js CHANGED
@@ -1279,6 +1279,184 @@ server.tool("scrape_page", "Smart page scraper — auto-detect and extract main
1279
1279
  })()`);
1280
1280
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1281
1281
  });
1282
+ // ── Tools: Compound (reduce round-trips) ───────────────────────────────────
1283
+ server.tool("wait_and_snapshot", "Wait for selector/text then return snapshot. Combines wait_for + browser_snapshot in one call.", {
1284
+ selector: z.string().default("").describe("CSS selector to wait for"),
1285
+ text: z.string().default("").describe("Text to wait for"),
1286
+ state: z.enum(["visible", "hidden", "attached", "detached"]).default("visible"),
1287
+ timeout: z.number().default(10000),
1288
+ }, async ({ selector, text, state, timeout }) => {
1289
+ const page = getPage();
1290
+ if (selector) {
1291
+ await page.locator(selector).first().waitFor({ state, timeout });
1292
+ }
1293
+ else if (text) {
1294
+ await page.getByText(text).first().waitFor({ state, timeout });
1295
+ }
1296
+ const elements = await page.evaluate(SNAPSHOT_JS) || [];
1297
+ const snap = formatSnapshot(elements, page.url(), await page.title());
1298
+ return { content: [{ type: "text", text: snap }] };
1299
+ });
1300
+ server.tool("back_and_snapshot", "Navigate back + return snapshot.", {}, async () => {
1301
+ const page = getPage();
1302
+ await page.goBack({ waitUntil: "domcontentloaded", timeout: 15000 });
1303
+ await page.waitForTimeout(500);
1304
+ const elements = await page.evaluate(SNAPSHOT_JS) || [];
1305
+ const snap = formatSnapshot(elements, page.url(), await page.title());
1306
+ return { content: [{ type: "text", text: snap }] };
1307
+ });
1308
+ server.tool("reload_and_snapshot", "Reload page + return snapshot.", {}, async () => {
1309
+ const page = getPage();
1310
+ await page.reload({ waitUntil: "domcontentloaded", timeout: 15000 });
1311
+ await page.waitForTimeout(500);
1312
+ const elements = await page.evaluate(SNAPSHOT_JS) || [];
1313
+ const snap = formatSnapshot(elements, page.url(), await page.title());
1314
+ return { content: [{ type: "text", text: snap }] };
1315
+ });
1316
+ server.tool("click_and_snapshot", "Click element by ref + wait + return snapshot. Perfect for buttons that trigger navigation/dialog.", {
1317
+ ref: z.string().describe("Element ref from browser_snapshot"),
1318
+ wait_ms: z.number().default(1500).describe("Wait after click before snapshot"),
1319
+ }, async ({ ref, wait_ms }) => {
1320
+ const page = getPage();
1321
+ const loc = page.locator(`[data-mcp-ref="${ref}"]`).first();
1322
+ try {
1323
+ await loc.click({ timeout: 5000 });
1324
+ }
1325
+ catch {
1326
+ await loc.evaluate((el) => el.click());
1327
+ }
1328
+ await page.waitForTimeout(wait_ms);
1329
+ const elements = await page.evaluate(SNAPSHOT_JS) || [];
1330
+ const snap = formatSnapshot(elements, page.url(), await page.title());
1331
+ return { content: [{ type: "text", text: snap }] };
1332
+ });
1333
+ // ── Tools: Smart Selectors (no snapshot needed) ────────────────────────────
1334
+ server.tool("find_by_text", "Find element by visible text — returns ref ID or null. Skip browser_snapshot if you know exact text.", {
1335
+ text: z.string().describe("Visible text to search for"),
1336
+ exact: z.boolean().default(true),
1337
+ }, async ({ text, exact }) => {
1338
+ const page = getPage();
1339
+ const loc = page.getByText(text, { exact }).first();
1340
+ const count = await loc.count();
1341
+ if (count === 0) {
1342
+ return { content: [{ type: "text", text: `No element found with text "${text}"` }] };
1343
+ }
1344
+ // Tag with ref
1345
+ const info = await loc.evaluate((el) => {
1346
+ const ref = 'f' + Math.floor(Math.random() * 10000);
1347
+ el.setAttribute('data-mcp-ref', ref);
1348
+ return {
1349
+ ref,
1350
+ tag: el.tagName.toLowerCase(),
1351
+ role: el.getAttribute('role') || '',
1352
+ text: (el.innerText || el.value || '').trim().slice(0, 100),
1353
+ href: el.href || '',
1354
+ };
1355
+ });
1356
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
1357
+ });
1358
+ server.tool("find_by_label", "Find input element by its label text (<label>). Returns ref.", {
1359
+ label: z.string().describe("Label text (e.g. 'Email', 'Password')"),
1360
+ }, async ({ label }) => {
1361
+ const page = getPage();
1362
+ const loc = page.getByLabel(label).first();
1363
+ const count = await loc.count();
1364
+ if (count === 0) {
1365
+ return { content: [{ type: "text", text: `No input found with label "${label}"` }] };
1366
+ }
1367
+ const info = await loc.evaluate((el) => {
1368
+ const ref = 'l' + Math.floor(Math.random() * 10000);
1369
+ el.setAttribute('data-mcp-ref', ref);
1370
+ return {
1371
+ ref,
1372
+ tag: el.tagName.toLowerCase(),
1373
+ type: el.type || '',
1374
+ name: el.name || '',
1375
+ placeholder: el.placeholder || '',
1376
+ value: el.value || '',
1377
+ };
1378
+ });
1379
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
1380
+ });
1381
+ server.tool("find_by_placeholder", "Find input by placeholder text. Returns ref.", {
1382
+ placeholder: z.string(),
1383
+ }, async ({ placeholder }) => {
1384
+ const page = getPage();
1385
+ const loc = page.getByPlaceholder(placeholder).first();
1386
+ const count = await loc.count();
1387
+ if (count === 0) {
1388
+ return { content: [{ type: "text", text: `No input with placeholder "${placeholder}"` }] };
1389
+ }
1390
+ const info = await loc.evaluate((el) => {
1391
+ const ref = 'p' + Math.floor(Math.random() * 10000);
1392
+ el.setAttribute('data-mcp-ref', ref);
1393
+ return {
1394
+ ref, tag: el.tagName.toLowerCase(), type: el.type || '', placeholder: el.placeholder || '',
1395
+ };
1396
+ });
1397
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
1398
+ });
1399
+ // ── Tools: Cookie Portability ──────────────────────────────────────────────
1400
+ server.tool("cookie_export", "Export all cookies as JSON string. Use with cookie_import to transfer session.", {
1401
+ domain: z.string().default("").describe("Filter by domain (empty = all)"),
1402
+ }, async ({ domain }) => {
1403
+ if (!browserContext)
1404
+ throw new Error("Browser not running.");
1405
+ let cookies = await browserContext.cookies();
1406
+ if (domain)
1407
+ cookies = cookies.filter(c => c.domain.includes(domain));
1408
+ return { content: [{ type: "text", text: JSON.stringify(cookies, null, 2) }] };
1409
+ });
1410
+ server.tool("cookie_import", "Import cookies from JSON (from cookie_export). Restores session state.", {
1411
+ cookies_json: z.string().describe("JSON array of cookies"),
1412
+ }, async ({ cookies_json }) => {
1413
+ if (!browserContext)
1414
+ throw new Error("Browser not running.");
1415
+ let cookies;
1416
+ try {
1417
+ cookies = JSON.parse(cookies_json);
1418
+ if (!Array.isArray(cookies))
1419
+ throw new Error("not an array");
1420
+ }
1421
+ catch (e) {
1422
+ return { content: [{ type: "text", text: `Invalid cookies JSON: ${e.message}` }] };
1423
+ }
1424
+ await browserContext.addCookies(cookies);
1425
+ return { content: [{ type: "text", text: `Imported ${cookies.length} cookies.` }] };
1426
+ });
1427
+ // ── Tools: Page Stats (debug/decision) ─────────────────────────────────────
1428
+ server.tool("page_stats", "Page statistics: element count, size, load metrics. Use to decide extraction strategy.", {}, async () => {
1429
+ const page = getPage();
1430
+ const stats = await page.evaluate(`(() => {
1431
+ var all = document.querySelectorAll('*').length;
1432
+ var interactive = document.querySelectorAll('button, a, input, select, textarea, [role="button"], [role="link"]').length;
1433
+ var images = document.querySelectorAll('img').length;
1434
+ var forms = document.querySelectorAll('form').length;
1435
+ var iframes = document.querySelectorAll('iframe').length;
1436
+ var scripts = document.querySelectorAll('script').length;
1437
+ var bodyTextLen = (document.body.innerText || '').length;
1438
+ var htmlLen = document.documentElement.outerHTML.length;
1439
+ var perf = window.performance && window.performance.timing ? {
1440
+ domComplete: window.performance.timing.domComplete - window.performance.timing.navigationStart,
1441
+ loadEnd: window.performance.timing.loadEventEnd - window.performance.timing.navigationStart,
1442
+ } : null;
1443
+ return {
1444
+ url: location.href,
1445
+ title: document.title,
1446
+ total_elements: all,
1447
+ interactive_elements: interactive,
1448
+ images: images,
1449
+ forms: forms,
1450
+ iframes: iframes,
1451
+ scripts: scripts,
1452
+ body_text_length: bodyTextLen,
1453
+ html_size: htmlLen,
1454
+ performance_ms: perf,
1455
+ recommendation: all > 3000 ? 'Use extract_structured or scrape_page (heavy page)' : 'browser_snapshot OK',
1456
+ };
1457
+ })()`);
1458
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
1459
+ });
1282
1460
  // ── Start Server ───────────────────────────────────────────────────────────
1283
1461
  async function main() {
1284
1462
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-camoufox",
3
- "version": "0.4.9",
4
- "description": "MCP server for stealth browser automation via Camoufox — 39 tools, Chrome DevTools MCP-level power with anti-bot stealth",
3
+ "version": "0.5.1",
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",
7
7
  "bin": {