crawlforge-mcp-server 4.2.11 → 4.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crawlforge-mcp-server",
3
- "version": "4.2.11",
3
+ "version": "4.2.12",
4
4
  "description": "CrawlForge MCP Server - Professional Model Context Protocol server with 23 web scraping, crawling, and content processing tools. Defaults to local Ollama for LLM extraction (no API key needed); OpenAI/Anthropic available as opt-in. v4.0 adds Markdown-first output, pre-built site templates, Camoufox stealth engine, and cost transparency.",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -899,7 +899,20 @@ server.registerTool("stealth_mode", {
899
899
  case 'create_page': {
900
900
  if (!contextId) throw new Error('contextId is required for create_page operation');
901
901
  const page = await stealthBrowserManager.createStealthPage(contextId);
902
- result = { pageCreated: true, contextId, url: urlToTest ? await page.goto(urlToTest) : null };
902
+ let navigation = null;
903
+ if (urlToTest) {
904
+ // page.goto returns a Playwright Response handle, which is not
905
+ // JSON-serializable — extract just the useful navigation details.
906
+ const response = await page.goto(urlToTest);
907
+ navigation = {
908
+ requestedUrl: urlToTest,
909
+ finalUrl: page.url(),
910
+ status: response ? response.status() : null,
911
+ ok: response ? response.ok() : null,
912
+ title: await page.title().catch(() => null)
913
+ };
914
+ }
915
+ result = { pageCreated: true, contextId, navigation };
903
916
  break;
904
917
  }
905
918
  case 'get_stats':
@@ -390,8 +390,11 @@ export class StealthBrowserManager {
390
390
  * Generate advanced browser fingerprint with enhanced randomization
391
391
  */
392
392
  generateAdvancedFingerprint(config = {}) {
393
+ // Select the OS once and thread it through UA, headers, and hardware so
394
+ // navigator.platform / sec-ch-ua-platform / userAgent stay consistent.
395
+ const selectedOS = this.selectOS(config);
393
396
  const fingerprint = {
394
- userAgent: this.selectRealisticUserAgent(config),
397
+ userAgent: this.selectRealisticUserAgent(config, selectedOS),
395
398
  viewport: config.customViewport || this.selectWeightedViewport(),
396
399
  timezone: config.timezone || this.selectTimezone(),
397
400
  deviceScaleFactor: this.randomFloat(1, 2, 1),
@@ -400,13 +403,13 @@ export class StealthBrowserManager {
400
403
  colorScheme: Math.random() < 0.3 ? 'dark' : 'light',
401
404
  reducedMotion: Math.random() < 0.1 ? 'reduce' : 'no-preference',
402
405
  forcedColors: Math.random() < 0.05 ? 'active' : 'none',
403
- headers: this.generateAdvancedHeaders(config),
406
+ headers: this.generateAdvancedHeaders(config, selectedOS),
404
407
  webRTC: this.generateWebRTCConfig(config),
405
408
  canvas: this.generateAdvancedCanvasFingerprint(),
406
409
  webGL: this.generateAdvancedWebGLFingerprint(),
407
410
  audioContext: this.generateAudioContextFingerprint(),
408
411
  mediaDevices: this.generateMediaDevicesFingerprint(),
409
- hardware: this.generateHardwareFingerprint(),
412
+ hardware: this.generateHardwareFingerprint(selectedOS),
410
413
  fonts: this.generateAdvancedFontList(),
411
414
  plugins: this.generateAdvancedPluginList(),
412
415
  geolocation: this.generateRealisticGeolocation(),
@@ -417,10 +420,34 @@ export class StealthBrowserManager {
417
420
  return fingerprint;
418
421
  }
419
422
 
423
+ /**
424
+ * Choose a single OS ('windows' | 'macos' | 'linux') for a fingerprint.
425
+ * A custom UA pins the OS to whatever that UA reports; a non-random UA pins
426
+ * to windows (the default pool below); otherwise weighted-random.
427
+ */
428
+ selectOS(config = {}) {
429
+ if (config.customUserAgent) {
430
+ return this.inferOSFromUserAgent(config.customUserAgent);
431
+ }
432
+ if (!config.useRandomUserAgent) {
433
+ return 'windows';
434
+ }
435
+ return this.weightedRandom(this.osDistribution);
436
+ }
437
+
438
+ /**
439
+ * Infer the OS key from a user-agent string.
440
+ */
441
+ inferOSFromUserAgent(ua = '') {
442
+ if (/Macintosh|Mac OS X/i.test(ua)) return 'macos';
443
+ if (/Linux|X11|CrOS/i.test(ua)) return 'linux';
444
+ return 'windows';
445
+ }
446
+
420
447
  /**
421
448
  * Select realistic user agent based on market distribution
422
449
  */
423
- selectRealisticUserAgent(config) {
450
+ selectRealisticUserAgent(config, selectedOS) {
424
451
  if (config.customUserAgent) {
425
452
  return config.customUserAgent;
426
453
  }
@@ -429,9 +456,10 @@ export class StealthBrowserManager {
429
456
  return this.userAgentPools.chrome.windows[0];
430
457
  }
431
458
 
432
- // Select OS based on distribution
433
- const selectedOS = this.weightedRandom(this.osDistribution);
434
-
459
+ // Use the OS chosen once for this fingerprint (falls back to a fresh draw
460
+ // if called without one, preserving the original standalone behavior).
461
+ selectedOS = selectedOS || this.weightedRandom(this.osDistribution);
462
+
435
463
  // Select browser based on distribution and OS compatibility
436
464
  let availableBrowsers = { ...this.browserDistribution };
437
465
  if (selectedOS === 'linux' && availableBrowsers.safari) {
@@ -469,7 +497,7 @@ export class StealthBrowserManager {
469
497
  /**
470
498
  * Generate advanced HTTP headers with realistic patterns
471
499
  */
472
- generateAdvancedHeaders(config) {
500
+ generateAdvancedHeaders(config, selectedOS) {
473
501
  const headers = {
474
502
  'Accept-Language': `${(config.locale || 'en-US').toLowerCase()},en;q=0.9`,
475
503
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
@@ -481,7 +509,7 @@ export class StealthBrowserManager {
481
509
  'Sec-Fetch-Site': 'none',
482
510
  'Sec-Fetch-User': '?1',
483
511
  'sec-ch-ua-mobile': '?0',
484
- 'sec-ch-ua-platform': this.generateSecChUaPlatform()
512
+ 'sec-ch-ua-platform': this.generateSecChUaPlatform(selectedOS)
485
513
  };
486
514
 
487
515
  // Add sec-ch-ua header
@@ -522,14 +550,14 @@ export class StealthBrowserManager {
522
550
  /**
523
551
  * Generate sec-ch-ua-platform header
524
552
  */
525
- generateSecChUaPlatform() {
553
+ generateSecChUaPlatform(selectedOS) {
526
554
  const platforms = {
527
555
  windows: '"Windows"',
528
556
  macos: '"macOS"',
529
557
  linux: '"Linux"'
530
558
  };
531
-
532
- const selectedOS = this.weightedRandom(this.osDistribution);
559
+
560
+ selectedOS = selectedOS || this.weightedRandom(this.osDistribution);
533
561
  return platforms[selectedOS] || '"Windows"';
534
562
  }
535
563
 
@@ -746,7 +774,9 @@ export class StealthBrowserManager {
746
774
  /**
747
775
  * Generate realistic hardware fingerprint
748
776
  */
749
- generateHardwareFingerprint() {
777
+ generateHardwareFingerprint(selectedOS) {
778
+ selectedOS = selectedOS || this.weightedRandom(this.osDistribution);
779
+
750
780
  const processors = [
751
781
  { cores: 4, threads: 8, name: 'Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz' },
752
782
  { cores: 6, threads: 12, name: 'Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz' },
@@ -755,31 +785,33 @@ export class StealthBrowserManager {
755
785
  { cores: 6, threads: 6, name: 'AMD Ryzen 5 3600 6-Core Processor' },
756
786
  { cores: 8, threads: 16, name: 'AMD Ryzen 7 3700X 8-Core Processor' }
757
787
  ];
758
-
788
+
759
789
  const selectedProcessor = processors[Math.floor(Math.random() * processors.length)];
760
-
790
+
761
791
  return {
762
792
  hardwareConcurrency: selectedProcessor.threads,
763
793
  processor: selectedProcessor.name,
764
- architecture: Math.random() < 0.9 ? 'x86_64' : 'arm64',
794
+ architecture: 'x86_64',
765
795
  memory: Math.floor(Math.random() * 24) + 8, // 8-32 GB
766
796
  deviceMemory: Math.pow(2, Math.floor(Math.random() * 3) + 3), // 8, 16, or 32 GB
767
- platform: this.selectRealisticPlatform()
797
+ platform: this.selectRealisticPlatform(selectedOS)
768
798
  };
769
799
  }
770
800
 
771
801
  /**
772
- * Select realistic platform based on distribution
802
+ * Map the chosen OS to its navigator.platform value so it stays consistent
803
+ * with the user-agent and sec-ch-ua-platform header.
773
804
  */
774
- selectRealisticPlatform() {
775
- const platforms = {
776
- 'Win32': 0.75,
777
- 'MacIntel': 0.15,
778
- 'Linux x86_64': 0.08,
779
- 'Linux armv7l': 0.02
780
- };
781
-
782
- return this.weightedRandom(platforms);
805
+ selectRealisticPlatform(selectedOS) {
806
+ switch (selectedOS) {
807
+ case 'macos':
808
+ return 'MacIntel';
809
+ case 'linux':
810
+ return 'Linux x86_64';
811
+ case 'windows':
812
+ default:
813
+ return 'Win32';
814
+ }
783
815
  }
784
816
 
785
817
  /**