scorchcrawl-mcp 2.0.1 → 2.1.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.
- package/dist/index.js +2 -29
- package/dist/response-utils.js +4 -272
- package/package.json +4 -9
- package/scripts/patch-copilot-sdk.cjs +0 -21
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import { FastMCP } from './lib/fastmcp/index.js';
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import ScorchClient from './lib/scorch-client/index.js';
|
|
6
6
|
import { localScrape, isLocalProxyEnabled, getCleanApiUrl } from './local-scraper.js';
|
|
7
|
-
import { getCopilotClient } from './copilot-client.js';
|
|
8
7
|
import { mapError, safeExecute, processResponse, processResponseSync, } from './response-utils.js';
|
|
9
8
|
dotenv.config({ debug: false, quiet: true });
|
|
10
9
|
function extractApiKey(headers) {
|
|
@@ -20,22 +19,6 @@ function extractApiKey(headers) {
|
|
|
20
19
|
}
|
|
21
20
|
return undefined;
|
|
22
21
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Extract a GitHub / Copilot token from request headers.
|
|
25
|
-
* Clients set one of:
|
|
26
|
-
* x-copilot-token: <token>
|
|
27
|
-
* x-github-token: <token>
|
|
28
|
-
* If neither header is present the server-wide env var
|
|
29
|
-
* GITHUB_TOKEN is used as a fallback.
|
|
30
|
-
*/
|
|
31
|
-
function extractCopilotToken(headers) {
|
|
32
|
-
const copilotHeader = (headers['x-copilot-token'] ||
|
|
33
|
-
headers['x-github-token']);
|
|
34
|
-
if (copilotHeader) {
|
|
35
|
-
return Array.isArray(copilotHeader) ? copilotHeader[0] : copilotHeader;
|
|
36
|
-
}
|
|
37
|
-
return process.env.GITHUB_TOKEN || undefined;
|
|
38
|
-
}
|
|
39
22
|
function removeEmptyTopLevel(obj) {
|
|
40
23
|
const out = {};
|
|
41
24
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -90,14 +73,12 @@ const server = new FastMCP({
|
|
|
90
73
|
logger: new ConsoleLogger(),
|
|
91
74
|
roots: { enabled: false },
|
|
92
75
|
authenticate: async (request) => {
|
|
93
|
-
// Extract per-user Copilot token (falls back to env var)
|
|
94
|
-
const copilotToken = extractCopilotToken(request.headers);
|
|
95
76
|
if (process.env.CLOUD_SERVICE === 'true') {
|
|
96
77
|
const apiKey = extractApiKey(request.headers);
|
|
97
78
|
if (!apiKey) {
|
|
98
79
|
throw new Error('API key is required');
|
|
99
80
|
}
|
|
100
|
-
return { scraperApiKey: apiKey
|
|
81
|
+
return { scraperApiKey: apiKey };
|
|
101
82
|
}
|
|
102
83
|
else {
|
|
103
84
|
// For self-hosted instances, API key is optional if SCORCHCRAWL_API_URL is provided
|
|
@@ -105,7 +86,7 @@ const server = new FastMCP({
|
|
|
105
86
|
console.error('Either SCORCHCRAWL_API_KEY or SCORCHCRAWL_API_URL must be provided');
|
|
106
87
|
process.exit(1);
|
|
107
88
|
}
|
|
108
|
-
return { scraperApiKey: process.env.SCORCHCRAWL_API_KEY
|
|
89
|
+
return { scraperApiKey: process.env.SCORCHCRAWL_API_KEY };
|
|
109
90
|
}
|
|
110
91
|
},
|
|
111
92
|
// Lightweight health endpoint for LB checks
|
|
@@ -348,8 +329,6 @@ ${SAFE_MODE
|
|
|
348
329
|
});
|
|
349
330
|
return await processResponse(res, {
|
|
350
331
|
url: String(url),
|
|
351
|
-
getCopilotClientFn: getCopilotClient,
|
|
352
|
-
copilotToken: session?.copilotToken,
|
|
353
332
|
});
|
|
354
333
|
}
|
|
355
334
|
// SPA detected: the local fetch returned a JS-only shell.
|
|
@@ -365,8 +344,6 @@ ${SAFE_MODE
|
|
|
365
344
|
});
|
|
366
345
|
return await processResponse(res, {
|
|
367
346
|
url: String(url),
|
|
368
|
-
getCopilotClientFn: getCopilotClient,
|
|
369
|
-
copilotToken: session?.copilotToken,
|
|
370
347
|
});
|
|
371
348
|
}
|
|
372
349
|
// Errors from local scraper
|
|
@@ -381,8 +358,6 @@ ${SAFE_MODE
|
|
|
381
358
|
}
|
|
382
359
|
return await processResponse(localResult, {
|
|
383
360
|
url: String(url),
|
|
384
|
-
getCopilotClientFn: getCopilotClient,
|
|
385
|
-
copilotToken: session?.copilotToken,
|
|
386
361
|
});
|
|
387
362
|
}
|
|
388
363
|
// --- Normal mode: use remote scraping API ---
|
|
@@ -394,8 +369,6 @@ ${SAFE_MODE
|
|
|
394
369
|
});
|
|
395
370
|
return await processResponse(res, {
|
|
396
371
|
url: String(url),
|
|
397
|
-
getCopilotClientFn: getCopilotClient,
|
|
398
|
-
copilotToken: session?.copilotToken,
|
|
399
372
|
});
|
|
400
373
|
}, { tool: 'scorch_scrape', url: String(url) });
|
|
401
374
|
},
|
package/dist/response-utils.js
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Response Processing Utilities
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Two layers of intelligent response handling for MCP tool results:
|
|
5
5
|
* 1. Error Mapping — classifies errors and returns actionable guidance
|
|
6
6
|
* 2. Content Truncation — smart paragraph-boundary truncation with notices
|
|
7
|
-
* 3. AI Summarization — Copilot SDK-powered content compression (optional)
|
|
8
7
|
*
|
|
9
8
|
* No competitor MCP scraper implements any of these. This is a ScorchCrawl
|
|
10
9
|
* differentiator.
|
|
11
10
|
*/
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
11
|
// ---------------------------------------------------------------------------
|
|
14
12
|
// Configuration (from environment)
|
|
15
13
|
// ---------------------------------------------------------------------------
|
|
16
14
|
const MAX_CONTENT_CHARS = parseInt(process.env.SCORCHCRAWL_MAX_CONTENT_CHARS || '25000', 10);
|
|
17
|
-
const SUMMARIZE_AFTER_WORDS = parseInt(process.env.SCORCHCRAWL_SUMMARIZE_AFTER_WORDS || '5000', 10);
|
|
18
|
-
const SUMMARIZE_MODEL = process.env.SCORCHCRAWL_SUMMARIZE_MODEL || 'gpt-4o';
|
|
19
|
-
const SUMMARIZE_CACHE_SIZE = parseInt(process.env.SCORCHCRAWL_SUMMARIZE_CACHE_SIZE || '100', 10);
|
|
20
|
-
const SUMMARIZE_MAX_PER_MINUTE = parseInt(process.env.SCORCHCRAWL_SUMMARIZE_MAX_PER_MINUTE || '10', 10);
|
|
21
|
-
const SUMMARIZE_TIMEOUT_MS = parseInt(process.env.SCORCHCRAWL_SUMMARIZE_TIMEOUT_MS || '300000', 10);
|
|
22
15
|
/**
|
|
23
16
|
* Classify a raw error (from fetch, SDK, or engine) into a structured
|
|
24
17
|
* MappedError with actionable guidance for the LLM.
|
|
@@ -335,205 +328,6 @@ function buildTruncationNotice(shownChars, originalChars) {
|
|
|
335
328
|
return `\n\n---\n[Content truncated. Showing ~${Math.round(shownChars / 1000)}k of ~${Math.round(originalChars / 1000)}k characters.\nTo get specific information, try:\n- Use JSON format with a schema to extract only the data you need\n- Add onlyMainContent: true to exclude navigation/footers\n- Use scorch_map to find a more specific page URL]`;
|
|
336
329
|
}
|
|
337
330
|
// ---------------------------------------------------------------------------
|
|
338
|
-
// Feature 3: AI Summarization
|
|
339
|
-
// ---------------------------------------------------------------------------
|
|
340
|
-
/**
|
|
341
|
-
* LRU cache for summarization results.
|
|
342
|
-
* Key: sha256(url + first 1000 chars of markdown)
|
|
343
|
-
* Value: { summary, timestamp }
|
|
344
|
-
*/
|
|
345
|
-
const summaryCache = new Map();
|
|
346
|
-
const CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
347
|
-
/** Sliding window for summarization rate limiting */
|
|
348
|
-
const summarizationTimestamps = [];
|
|
349
|
-
/** Word count helper */
|
|
350
|
-
export function wordCount(text) {
|
|
351
|
-
return text.split(/\s+/).filter(Boolean).length;
|
|
352
|
-
}
|
|
353
|
-
/** Generate a cache key for summarization */
|
|
354
|
-
function summaryKey(url, markdown) {
|
|
355
|
-
const fingerprint = url + markdown.slice(0, 1000);
|
|
356
|
-
return createHash('sha256').update(fingerprint).digest('hex');
|
|
357
|
-
}
|
|
358
|
-
/** Check if we can make another summarization call (rate limit) */
|
|
359
|
-
function canSummarize() {
|
|
360
|
-
if (SUMMARIZE_MAX_PER_MINUTE <= 0)
|
|
361
|
-
return false;
|
|
362
|
-
const now = Date.now();
|
|
363
|
-
const windowStart = now - 60_000;
|
|
364
|
-
// Purge old entries
|
|
365
|
-
while (summarizationTimestamps.length > 0 && summarizationTimestamps[0] < windowStart) {
|
|
366
|
-
summarizationTimestamps.shift();
|
|
367
|
-
}
|
|
368
|
-
return summarizationTimestamps.length < SUMMARIZE_MAX_PER_MINUTE;
|
|
369
|
-
}
|
|
370
|
-
/** Record a summarization call for rate limiting */
|
|
371
|
-
function recordSummarization() {
|
|
372
|
-
summarizationTimestamps.push(Date.now());
|
|
373
|
-
}
|
|
374
|
-
/** Evict oldest entries if cache exceeds max size */
|
|
375
|
-
function evictCache() {
|
|
376
|
-
if (summaryCache.size <= SUMMARIZE_CACHE_SIZE)
|
|
377
|
-
return;
|
|
378
|
-
// Find and delete the oldest entries
|
|
379
|
-
const entries = [...summaryCache.entries()].sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
380
|
-
const toDelete = entries.slice(0, entries.length - SUMMARIZE_CACHE_SIZE);
|
|
381
|
-
for (const [key] of toDelete) {
|
|
382
|
-
summaryCache.delete(key);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Summarize markdown content using the Copilot SDK if available.
|
|
387
|
-
* Falls back to truncation on failure or when no LLM is available.
|
|
388
|
-
*
|
|
389
|
-
* @param markdown - The full markdown content to potentially summarize
|
|
390
|
-
* @param url - Source URL (used for cache key)
|
|
391
|
-
* @param getCopilotClientFn - Function to get a CopilotClient instance
|
|
392
|
-
* @param copilotToken - Optional per-user Copilot token
|
|
393
|
-
* @returns Processed markdown with summarization metadata
|
|
394
|
-
*/
|
|
395
|
-
export async function summarizeIfNeeded(markdown, url, getCopilotClientFn, copilotToken) {
|
|
396
|
-
// Check if summarization is disabled
|
|
397
|
-
if (SUMMARIZE_AFTER_WORDS <= 0) {
|
|
398
|
-
return { markdown, summarized: false };
|
|
399
|
-
}
|
|
400
|
-
const words = wordCount(markdown);
|
|
401
|
-
if (words <= SUMMARIZE_AFTER_WORDS) {
|
|
402
|
-
return { markdown, summarized: false };
|
|
403
|
-
}
|
|
404
|
-
// Check cache
|
|
405
|
-
const key = summaryKey(url, markdown);
|
|
406
|
-
const cached = summaryCache.get(key);
|
|
407
|
-
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
408
|
-
return {
|
|
409
|
-
markdown: cached.summary,
|
|
410
|
-
summarized: true,
|
|
411
|
-
meta: {
|
|
412
|
-
_summarized: true,
|
|
413
|
-
_originalWordCount: words,
|
|
414
|
-
_summaryWordCount: cached.wordCount,
|
|
415
|
-
_summarizedBy: SUMMARIZE_MODEL,
|
|
416
|
-
_cachedResult: true,
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
// Check if Copilot client is available
|
|
421
|
-
if (!getCopilotClientFn) {
|
|
422
|
-
return { markdown, summarized: false };
|
|
423
|
-
}
|
|
424
|
-
// Check rate limit
|
|
425
|
-
if (!canSummarize()) {
|
|
426
|
-
return { markdown, summarized: false };
|
|
427
|
-
}
|
|
428
|
-
try {
|
|
429
|
-
const client = await getCopilotClientFn(copilotToken);
|
|
430
|
-
if (!client) {
|
|
431
|
-
return { markdown, summarized: false };
|
|
432
|
-
}
|
|
433
|
-
const targetWords = Math.min(SUMMARIZE_AFTER_WORDS, Math.floor(words * 0.3));
|
|
434
|
-
const sessionConfig = {
|
|
435
|
-
model: SUMMARIZE_MODEL,
|
|
436
|
-
systemMessage: {
|
|
437
|
-
mode: 'replace',
|
|
438
|
-
content: buildSummarizationPrompt(words, targetWords),
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
|
-
const session = await client.createSession(sessionConfig);
|
|
442
|
-
recordSummarization();
|
|
443
|
-
try {
|
|
444
|
-
const response = await session.sendAndWait({ prompt: markdown }, SUMMARIZE_TIMEOUT_MS);
|
|
445
|
-
// Extract content from response — handle multiple possible shapes
|
|
446
|
-
let summary = '';
|
|
447
|
-
if (response && typeof response === 'object') {
|
|
448
|
-
const r = response;
|
|
449
|
-
// Shape 1: { data: { content: "..." } } (most common)
|
|
450
|
-
if (r.data && typeof r.data === 'object') {
|
|
451
|
-
const d = r.data;
|
|
452
|
-
if (typeof d.content === 'string')
|
|
453
|
-
summary = d.content;
|
|
454
|
-
else if (typeof d.text === 'string')
|
|
455
|
-
summary = d.text;
|
|
456
|
-
}
|
|
457
|
-
// Shape 2: { content: "..." }
|
|
458
|
-
if (!summary && typeof r.content === 'string')
|
|
459
|
-
summary = r.content;
|
|
460
|
-
// Shape 3: { text: "..." }
|
|
461
|
-
if (!summary && typeof r.text === 'string')
|
|
462
|
-
summary = r.text;
|
|
463
|
-
// Shape 4: { message: { content: "..." } }
|
|
464
|
-
if (!summary && r.message && typeof r.message === 'object') {
|
|
465
|
-
const m = r.message;
|
|
466
|
-
if (typeof m.content === 'string')
|
|
467
|
-
summary = m.content;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
else if (typeof response === 'string') {
|
|
471
|
-
summary = response;
|
|
472
|
-
}
|
|
473
|
-
if (!summary || summary.length < 50) {
|
|
474
|
-
// Summarization returned garbage — fall back
|
|
475
|
-
console.error(`[Summarization] Response too short or empty (${summary.length} chars), response keys: ${response ? Object.keys(response).join(', ') : 'null'}`);
|
|
476
|
-
return { markdown, summarized: false };
|
|
477
|
-
}
|
|
478
|
-
const summaryWords = wordCount(summary);
|
|
479
|
-
// Cache the result
|
|
480
|
-
summaryCache.set(key, {
|
|
481
|
-
summary,
|
|
482
|
-
timestamp: Date.now(),
|
|
483
|
-
wordCount: summaryWords,
|
|
484
|
-
});
|
|
485
|
-
evictCache();
|
|
486
|
-
return {
|
|
487
|
-
markdown: summary,
|
|
488
|
-
summarized: true,
|
|
489
|
-
meta: {
|
|
490
|
-
_summarized: true,
|
|
491
|
-
_originalWordCount: words,
|
|
492
|
-
_summaryWordCount: summaryWords,
|
|
493
|
-
_summarizedBy: SUMMARIZE_MODEL,
|
|
494
|
-
},
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
finally {
|
|
498
|
-
try {
|
|
499
|
-
await session.destroy();
|
|
500
|
-
}
|
|
501
|
-
catch { /* ignore cleanup */ }
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
catch (err) {
|
|
505
|
-
// Summarization failed — fall back silently but log for debugging
|
|
506
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
507
|
-
const errStack = err instanceof Error ? err.stack : '';
|
|
508
|
-
console.error(`[Summarization] Failed for ${url}: ${errMsg}`);
|
|
509
|
-
if (errStack)
|
|
510
|
-
console.error(`[Summarization] Stack: ${errStack}`);
|
|
511
|
-
return {
|
|
512
|
-
markdown,
|
|
513
|
-
summarized: false,
|
|
514
|
-
meta: { _summarizationFailed: true, _summarizationError: errMsg },
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
/** Build the system prompt for content summarization */
|
|
519
|
-
function buildSummarizationPrompt(originalWords, targetWords) {
|
|
520
|
-
return `You are a content summarizer for a web scraping tool.
|
|
521
|
-
Summarize the following web page content, preserving:
|
|
522
|
-
- ALL factual data (numbers, dates, names, prices, specifications)
|
|
523
|
-
- Key arguments, conclusions, and findings
|
|
524
|
-
- Structural organization (use headings, lists)
|
|
525
|
-
- Any code snippets or technical details
|
|
526
|
-
|
|
527
|
-
Do NOT:
|
|
528
|
-
- Add opinions or analysis
|
|
529
|
-
- Omit specific data points (numbers, names, URLs)
|
|
530
|
-
- Change the meaning or emphasis of the content
|
|
531
|
-
- Add any preamble like "Here is a summary" — just output the summary directly
|
|
532
|
-
|
|
533
|
-
Original content word count: ${originalWords}
|
|
534
|
-
Target summary: ~${targetWords} words`;
|
|
535
|
-
}
|
|
536
|
-
// ---------------------------------------------------------------------------
|
|
537
331
|
// Combined Processing Pipeline
|
|
538
332
|
// ---------------------------------------------------------------------------
|
|
539
333
|
/**
|
|
@@ -559,85 +353,23 @@ export async function safeExecute(fn, context) {
|
|
|
559
353
|
}
|
|
560
354
|
}
|
|
561
355
|
/**
|
|
562
|
-
* Process a successful response: truncate content if needed
|
|
563
|
-
* optionally summarize via Copilot SDK.
|
|
356
|
+
* Process a successful response: truncate content if needed.
|
|
564
357
|
*
|
|
565
358
|
* Replaces the old `asText()` function.
|
|
566
359
|
*/
|
|
567
360
|
export async function processResponse(data, options) {
|
|
568
|
-
// Step 1: Try AI summarization on markdown content
|
|
569
|
-
if (!options?.skipSummarization && options?.getCopilotClientFn) {
|
|
570
|
-
const md = extractMarkdown(data);
|
|
571
|
-
const wc = md ? wordCount(md) : 0;
|
|
572
|
-
console.error(`[Summarization] Check: markdown=${md ? md.length : 0} chars, ${wc} words, threshold=${SUMMARIZE_AFTER_WORDS}`);
|
|
573
|
-
if (md && wc > SUMMARIZE_AFTER_WORDS && SUMMARIZE_AFTER_WORDS > 0) {
|
|
574
|
-
console.error(`[Summarization] Attempting summarization for ${options.url || 'unknown url'} (${wc} words)`);
|
|
575
|
-
const result = await summarizeIfNeeded(md, options.url || '', options.getCopilotClientFn, options.copilotToken);
|
|
576
|
-
if (result.summarized) {
|
|
577
|
-
// Replace the markdown in the data with the summary
|
|
578
|
-
const updated = injectMarkdown(data, result.markdown, result.meta || {});
|
|
579
|
-
return JSON.stringify(updated, null, 2);
|
|
580
|
-
}
|
|
581
|
-
// Summarization was attempted but failed — inject failure metadata and fall through to truncation
|
|
582
|
-
if (result.meta) {
|
|
583
|
-
console.error(`[Summarization] Failed, falling back to truncation. Meta:`, JSON.stringify(result.meta));
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
console.error(`[Summarization] Skipped: skipSummarization=${options?.skipSummarization}, hasCopilotFn=${!!options?.getCopilotClientFn}`);
|
|
589
|
-
}
|
|
590
|
-
// Step 2: Content truncation
|
|
591
361
|
const { result, wasTruncated } = truncateContent(data);
|
|
592
362
|
return JSON.stringify(result, null, 2);
|
|
593
363
|
}
|
|
594
364
|
/**
|
|
595
365
|
* Simple `asText` replacement for backward compatibility.
|
|
596
|
-
* Applies truncation
|
|
366
|
+
* Applies truncation only.
|
|
597
367
|
*/
|
|
598
368
|
export function processResponseSync(data) {
|
|
599
369
|
const { result } = truncateContent(data);
|
|
600
370
|
return JSON.stringify(result, null, 2);
|
|
601
371
|
}
|
|
602
372
|
// ---------------------------------------------------------------------------
|
|
603
|
-
// Helpers for extracting/injecting markdown from nested response objects
|
|
604
|
-
// ---------------------------------------------------------------------------
|
|
605
|
-
/** Extract markdown content from a scrape response, wherever it may be nested */
|
|
606
|
-
function extractMarkdown(data) {
|
|
607
|
-
if (!data || typeof data !== 'object')
|
|
608
|
-
return null;
|
|
609
|
-
const obj = data;
|
|
610
|
-
// Direct: data.markdown
|
|
611
|
-
if (typeof obj.markdown === 'string')
|
|
612
|
-
return obj.markdown;
|
|
613
|
-
// Nested: data.data.markdown
|
|
614
|
-
if (obj.data && typeof obj.data === 'object') {
|
|
615
|
-
const inner = obj.data;
|
|
616
|
-
if (typeof inner.markdown === 'string')
|
|
617
|
-
return inner.markdown;
|
|
618
|
-
}
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
/** Replace the markdown field in a response and merge metadata */
|
|
622
|
-
function injectMarkdown(data, newMarkdown, meta) {
|
|
623
|
-
const serialized = JSON.stringify(data);
|
|
624
|
-
const cloned = JSON.parse(serialized);
|
|
625
|
-
if (typeof cloned.markdown === 'string') {
|
|
626
|
-
cloned.markdown = newMarkdown;
|
|
627
|
-
Object.assign(cloned.metadata || cloned, meta);
|
|
628
|
-
return cloned;
|
|
629
|
-
}
|
|
630
|
-
if (cloned.data && typeof cloned.data === 'object') {
|
|
631
|
-
if (typeof cloned.data.markdown === 'string') {
|
|
632
|
-
cloned.data.markdown = newMarkdown;
|
|
633
|
-
Object.assign(cloned.data.metadata || cloned.data, meta);
|
|
634
|
-
return cloned;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
return cloned;
|
|
638
|
-
}
|
|
639
|
-
// ---------------------------------------------------------------------------
|
|
640
373
|
// Exports for testing
|
|
641
374
|
// ---------------------------------------------------------------------------
|
|
642
|
-
export { MAX_CONTENT_CHARS
|
|
643
|
-
export { summaryCache, summarizationTimestamps };
|
|
375
|
+
export { MAX_CONTENT_CHARS };
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scorchcrawl-mcp",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Open-source
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Open-source MCP server for web scraping with stealth bot-detection bypass.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"scorchcrawl-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
"scripts"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"publishConfig": {
|
|
14
13
|
"access": "public"
|
|
@@ -25,12 +24,10 @@
|
|
|
25
24
|
"lint": "eslint src/**/*.ts",
|
|
26
25
|
"lint:fix": "eslint src/**/*.ts --fix",
|
|
27
26
|
"format": "prettier --write .",
|
|
28
|
-
"prepare": "npm run build"
|
|
29
|
-
"postinstall": "node scripts/patch-copilot-sdk.cjs"
|
|
27
|
+
"prepare": "npm run build"
|
|
30
28
|
},
|
|
31
29
|
"license": "AGPL-3.0",
|
|
32
30
|
"dependencies": {
|
|
33
|
-
"@github/copilot-sdk": "^0.1.23",
|
|
34
31
|
"@mendable/firecrawl-js": "^4.9.3",
|
|
35
32
|
"cheerio": "^1.2.0",
|
|
36
33
|
"dotenv": "^17.2.2",
|
|
@@ -47,8 +44,6 @@
|
|
|
47
44
|
"mcp",
|
|
48
45
|
"scorchcrawl",
|
|
49
46
|
"web-scraping",
|
|
50
|
-
"copilot",
|
|
51
|
-
"copilot-sdk",
|
|
52
47
|
"stealth",
|
|
53
48
|
"bot-detection-bypass",
|
|
54
49
|
"crawler",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function patchCopilotSdkImport() {
|
|
5
|
-
let sessionPath;
|
|
6
|
-
try {
|
|
7
|
-
sessionPath = require.resolve('@github/copilot-sdk/dist/session.js');
|
|
8
|
-
} catch {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
13
|
-
const patched = content.replaceAll('"vscode-jsonrpc/node"', '"vscode-jsonrpc/node.js"');
|
|
14
|
-
|
|
15
|
-
if (patched !== content) {
|
|
16
|
-
fs.writeFileSync(sessionPath, patched, 'utf8');
|
|
17
|
-
process.stdout.write(`[postinstall] patched ${path.relative(process.cwd(), sessionPath)}\n`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
patchCopilotSdkImport();
|