scorchcrawl-mcp 1.2.0 → 2.0.0

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.
@@ -26,19 +26,6 @@ const SUMMARIZE_TIMEOUT_MS = parseInt(process.env.SCORCHCRAWL_SUMMARIZE_TIMEOUT_
26
26
  export function mapError(err) {
27
27
  const raw = normalizeErrorString(err);
28
28
  const lower = raw.toLowerCase();
29
- // --- CAPTCHA (checked before ACCESS_DENIED since 'challenge' overlaps) ---
30
- if (matches(lower, ['captcha', 'recaptcha', 'hcaptcha', 'turnstile'])) {
31
- return {
32
- code: 'CAPTCHA_REQUIRED',
33
- message: 'Page requires CAPTCHA verification.',
34
- suggestions: [
35
- 'Try proxy: "stealth" to avoid CAPTCHA triggers',
36
- 'Use scorch_agent which may navigate around CAPTCHAs',
37
- 'Try scorch_search to find the content from a cached source',
38
- ],
39
- originalError: raw,
40
- };
41
- }
42
29
  // --- HTTP status-based patterns ---
43
30
  if (matches(lower, ['403', 'forbidden', 'cloudflare', 'cf-ray', 'challenge', 'access denied'])) {
44
31
  return {
@@ -157,28 +144,6 @@ export function mapError(err) {
157
144
  originalError: raw,
158
145
  };
159
146
  }
160
- if (matches(lower, ['geo', 'geoblocked', 'not available in your', 'region', 'country restriction'])) {
161
- return {
162
- code: 'GEO_BLOCKED',
163
- message: 'Content is geo-restricted.',
164
- suggestions: [
165
- 'Use location parameter with a different country: location: { country: "US" }',
166
- 'Try proxy: "stealth" which may route through a different region',
167
- ],
168
- originalError: raw,
169
- };
170
- }
171
- if (matches(lower, ['paywall', 'subscribe', 'premium content', 'members only'])) {
172
- return {
173
- code: 'PAYWALL',
174
- message: 'Content is behind a paywall.',
175
- suggestions: [
176
- 'Try scorch_search to find freely available alternatives',
177
- 'Use scorch_map to find non-paywalled pages on the same site',
178
- ],
179
- originalError: raw,
180
- };
181
- }
182
147
  // --- Catch-all ---
183
148
  return {
184
149
  code: 'UNKNOWN_ERROR',
@@ -676,114 +641,3 @@ function injectMarkdown(data, newMarkdown, meta) {
676
641
  // ---------------------------------------------------------------------------
677
642
  export { MAX_CONTENT_CHARS, SUMMARIZE_AFTER_WORDS, SUMMARIZE_MODEL };
678
643
  export { summaryCache, summarizationTimestamps };
679
- // ---------------------------------------------------------------------------
680
- // Feature 4: Response Quality Scoring
681
- // ---------------------------------------------------------------------------
682
- /** Known anti-bot / blocked-page signatures */
683
- const ANTI_BOT_SIGNATURES = [
684
- // Cloudflare
685
- 'checking your browser', 'cf-browser-verification', 'just a moment',
686
- 'cf-challenge-running', 'cf_chl_opt', 'ray id:',
687
- // DataDome
688
- 'datadome', 'dd.js',
689
- // PerimeterX
690
- 'perimeterx', 'px-captcha', 'human challenge',
691
- // Incapsula / Imperva
692
- 'incapsula', 'imperva', '_incapsula_resource',
693
- // Generic
694
- 'access denied', 'bot detected', 'automated access',
695
- 'please verify you are a human', 'captcha',
696
- // Akamai
697
- 'akamai', 'ak_bmsc',
698
- // Shape Security
699
- 'shape security',
700
- ];
701
- /**
702
- * Score the quality of a scrape response (0-100).
703
- * Detects anti-bot pages, empty/minimal content, and common failure patterns.
704
- */
705
- export function scoreResponseQuality(data) {
706
- const issues = [];
707
- let score = 100;
708
- let antiBotDetected = false;
709
- let antiBotSystem;
710
- if (!data || typeof data !== 'object') {
711
- return { score: 0, antiBotDetected: false, issues: ['No response data'] };
712
- }
713
- const obj = data;
714
- const inner = (obj.data && typeof obj.data === 'object' ? obj.data : obj);
715
- // Check for explicit failure
716
- if (obj.success === false) {
717
- score -= 50;
718
- issues.push('Response indicates failure');
719
- }
720
- // Extract text content for analysis
721
- const md = (typeof inner.markdown === 'string' ? inner.markdown : '');
722
- const html = (typeof inner.html === 'string' ? inner.html : typeof inner.rawHtml === 'string' ? inner.rawHtml : '');
723
- const content = md || html;
724
- const lower = content.toLowerCase();
725
- // --- Anti-bot detection ---
726
- for (const sig of ANTI_BOT_SIGNATURES) {
727
- if (lower.includes(sig)) {
728
- antiBotDetected = true;
729
- // Identify the specific system
730
- if (sig.includes('cf-') || sig.includes('cloudflare') || sig === 'just a moment' || sig === 'checking your browser' || sig === 'ray id:') {
731
- antiBotSystem = 'Cloudflare';
732
- }
733
- else if (sig.includes('datadome')) {
734
- antiBotSystem = 'DataDome';
735
- }
736
- else if (sig.includes('perimeterx') || sig.includes('px-captcha')) {
737
- antiBotSystem = 'PerimeterX';
738
- }
739
- else if (sig.includes('incapsula') || sig.includes('imperva')) {
740
- antiBotSystem = 'Imperva/Incapsula';
741
- }
742
- else if (sig.includes('akamai') || sig.includes('ak_bmsc')) {
743
- antiBotSystem = 'Akamai';
744
- }
745
- else if (sig.includes('captcha')) {
746
- antiBotSystem = 'CAPTCHA';
747
- }
748
- else {
749
- antiBotSystem = 'Unknown';
750
- }
751
- score -= 40;
752
- issues.push(`Anti-bot protection detected: ${antiBotSystem}`);
753
- break;
754
- }
755
- }
756
- // --- Content length analysis ---
757
- if (content.length === 0) {
758
- score -= 30;
759
- issues.push('No content extracted');
760
- }
761
- else if (content.length < 100) {
762
- score -= 20;
763
- issues.push('Very minimal content (< 100 chars)');
764
- }
765
- else if (content.length < 500) {
766
- score -= 10;
767
- issues.push('Thin content (< 500 chars)');
768
- }
769
- // --- Navigation-only content detection (SPA shells) ---
770
- const navPatterns = ['sign in', 'log in', 'menu', 'navigation', 'footer', 'header'];
771
- const navCount = navPatterns.filter(p => lower.includes(p)).length;
772
- const contentWords = content.split(/\s+/).length;
773
- if (contentWords < 50 && navCount >= 3) {
774
- score -= 20;
775
- issues.push('Content appears to be navigation-only (possible SPA shell)');
776
- }
777
- // --- Error page detection ---
778
- const errorPagePatterns = ['page not found', '404', 'error occurred', 'something went wrong', 'under maintenance'];
779
- if (errorPagePatterns.some(p => lower.includes(p)) && contentWords < 200) {
780
- score -= 15;
781
- issues.push('Content appears to be an error page');
782
- }
783
- return {
784
- score: Math.max(0, Math.min(100, score)),
785
- antiBotDetected,
786
- ...(antiBotSystem && { antiBotSystem }),
787
- issues,
788
- };
789
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scorchcrawl-mcp",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "Open-source Copilot SDK-compliant MCP server for web scraping with stealth bot-detection bypass.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,8 +13,7 @@
13
13
  "access": "public"
14
14
  },
15
15
  "scripts": {
16
- "build": "tsc --noEmit && esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --external:effect --external:@valibot/to-json-schema --external:sury --minify --keep-names --banner:js='#!/usr/bin/env node\nimport{createRequire as __cr}from\"module\";const require=__cr(import.meta.url);' && chmod 755 dist/index.js",
17
- "build:tsc": "tsc",
16
+ "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
18
17
  "start": "node dist/index.js",
19
18
  "start:server": "HTTP_STREAMABLE_SERVER=true node dist/index.js",
20
19
  "dev": "tsc --watch",
@@ -63,7 +62,6 @@
63
62
  "@types/node": "^22.0.0",
64
63
  "@types/turndown": "^5.0.5",
65
64
  "@types/uuid": "^10.0.0",
66
- "esbuild": "^0.25.0",
67
65
  "vitest": "^3.2.1"
68
66
  }
69
67
  }