crawlforge-mcp-server 5.2.7 → 5.2.8
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/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/server.js +1 -1
- package/src/core/ResearchOrchestrator.js +31 -20
- package/src/core/llm/LLMManager.js +35 -15
package/CLAUDE.md
CHANGED
|
@@ -62,7 +62,7 @@ These guidelines are working if: fewer unnecessary changes in diffs, fewer rewri
|
|
|
62
62
|
|
|
63
63
|
CrawlForge MCP Server - A professional MCP (Model Context Protocol) server providing 28 web scraping, crawling, and content processing tools (5 inline + 23 advanced).
|
|
64
64
|
|
|
65
|
-
**Current Version:** 5.2.
|
|
65
|
+
**Current Version:** 5.2.8
|
|
66
66
|
|
|
67
67
|
## Development Commands
|
|
68
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlforge-mcp-server",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.8",
|
|
4
4
|
"mcpName": "io.github.mysleekdesigns/crawlforge-mcp-server",
|
|
5
5
|
"description": "CrawlForge MCP Server - Professional Model Context Protocol server with 28 web scraping, crawling, deep-research, and autonomous-extraction tools. Returns clean Markdown and structured JSON for Claude, Cursor, and any MCP client. Defaults to local Ollama for LLM extraction (no API key needed); OpenAI/Anthropic available as opt-in. Includes a unified multi-format scrape tool, an autonomous agent, pre-built site templates, and Camoufox stealth browsing.",
|
|
6
6
|
"main": "server.js",
|
package/server.js
CHANGED
|
@@ -100,7 +100,7 @@ const taskStore = createTaskStore({ logger });
|
|
|
100
100
|
// Create the server
|
|
101
101
|
const server = new McpServer({
|
|
102
102
|
name: "crawlforge",
|
|
103
|
-
version: "5.2.
|
|
103
|
+
version: "5.2.8",
|
|
104
104
|
description: "Production-ready MCP server with 28 web scraping, crawling, and content processing tools. Features MCP Resources (crawlforge://), Prompts, Sampling fallback, Elicitation, stealth browsing, deep research, structured extraction, real Google SERP rank tracking, Reddit search via community archives, change tracking, local-LLM extraction via Ollama, unified multi-format scrape, and autonomous agent tool.",
|
|
105
105
|
homepage: "https://www.crawlforge.dev",
|
|
106
106
|
icon: "https://www.crawlforge.dev/icon.png",
|
|
@@ -1254,7 +1254,7 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
1254
1254
|
return claimGroups
|
|
1255
1255
|
.filter(group => group.sourceCount >= 2 && group.avgCredibility >= 0.6)
|
|
1256
1256
|
.map(group => ({
|
|
1257
|
-
topic:
|
|
1257
|
+
topic: this.claimGroupLabel(group),
|
|
1258
1258
|
supportingClaims: group.claims.length,
|
|
1259
1259
|
supportingSources: group.sourceCount,
|
|
1260
1260
|
averageCredibility: group.avgCredibility,
|
|
@@ -1510,27 +1510,37 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
1510
1510
|
});
|
|
1511
1511
|
}
|
|
1512
1512
|
|
|
1513
|
+
// A claim group rendered for humans is its most credible claim. Claims are
|
|
1514
|
+
// extractive sentences from source content, so this is readable prose —
|
|
1515
|
+
// joining the group's keywords produces stopword-stripped gibberish
|
|
1516
|
+
// ("scraping server model context protocol server that...").
|
|
1517
|
+
mostCredibleClaim(group) {
|
|
1518
|
+
return group.claims.reduce(
|
|
1519
|
+
(best, c) => ((c.credibility || 0) > (best.credibility || 0) ? c : best),
|
|
1520
|
+
group.claims[0]
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// Compact label for a claim group: its best claim, cut at a word break.
|
|
1525
|
+
claimGroupLabel(group, maxChars = 120) {
|
|
1526
|
+
const claim = this.mostCredibleClaim(group)?.claim || '';
|
|
1527
|
+
if (claim.length <= maxChars) return claim;
|
|
1528
|
+
const cut = claim.slice(0, maxChars);
|
|
1529
|
+
const lastSpace = cut.lastIndexOf(' ');
|
|
1530
|
+
return (lastSpace > 40 ? cut.slice(0, lastSpace) : cut) + '…';
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1513
1533
|
generateKeyFindings(claimGroups, sources) {
|
|
1514
1534
|
return claimGroups
|
|
1515
1535
|
.filter(group => group.avgCredibility >= this.credibilityThreshold)
|
|
1516
1536
|
.sort((a, b) => b.consensusStrength - a.consensusStrength)
|
|
1517
1537
|
.slice(0, 10)
|
|
1518
|
-
.map(group => {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
(best, c) => ((c.credibility || 0) > (best.credibility || 0) ? c : best),
|
|
1525
|
-
group.claims[0]
|
|
1526
|
-
);
|
|
1527
|
-
return {
|
|
1528
|
-
finding: representative.claim,
|
|
1529
|
-
supportingClaims: group.claims.length,
|
|
1530
|
-
credibility: group.avgCredibility,
|
|
1531
|
-
sources: group.claims.map(c => c.source)
|
|
1532
|
-
};
|
|
1533
|
-
});
|
|
1538
|
+
.map(group => ({
|
|
1539
|
+
finding: this.mostCredibleClaim(group).claim,
|
|
1540
|
+
supportingClaims: group.claims.length,
|
|
1541
|
+
credibility: group.avgCredibility,
|
|
1542
|
+
sources: group.claims.map(c => c.source)
|
|
1543
|
+
}));
|
|
1534
1544
|
}
|
|
1535
1545
|
|
|
1536
1546
|
compileSupportingEvidence(sources) {
|
|
@@ -1584,10 +1594,11 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
1584
1594
|
);
|
|
1585
1595
|
|
|
1586
1596
|
weakAreas.forEach(area => {
|
|
1597
|
+
const label = this.claimGroupLabel(area);
|
|
1587
1598
|
gaps.push({
|
|
1588
|
-
area:
|
|
1599
|
+
area: label,
|
|
1589
1600
|
issue: 'Limited reliable sources',
|
|
1590
|
-
suggestion: `
|
|
1601
|
+
suggestion: `Corroborate with additional sources: "${label}"`
|
|
1591
1602
|
});
|
|
1592
1603
|
});
|
|
1593
1604
|
|
|
@@ -1609,7 +1620,7 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
1609
1620
|
recommendations.push({
|
|
1610
1621
|
type: 'gap_filling',
|
|
1611
1622
|
priority: 'medium',
|
|
1612
|
-
description: `Address
|
|
1623
|
+
description: `Address ${synthesis.gaps.length} under-sourced claim(s) — see researchGaps, e.g. "${synthesis.gaps[0].area}"`
|
|
1613
1624
|
});
|
|
1614
1625
|
}
|
|
1615
1626
|
|
|
@@ -306,10 +306,18 @@ Generate a JSON response with:
|
|
|
306
306
|
"confidence": 0.0-1.0,
|
|
307
307
|
"gaps": ["gap1", "gap2", ...],
|
|
308
308
|
"recommendations": ["rec1", "rec2", ...]
|
|
309
|
-
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Be brief: summary at most 3 sentences; each array at most 5 items, one short sentence each.`;
|
|
310
312
|
|
|
311
313
|
const findingsText = limitedFindings
|
|
312
|
-
.map((finding, index) =>
|
|
314
|
+
.map((finding, index) => {
|
|
315
|
+
// A finding can be a whole flattened page section (sitemap dumps run
|
|
316
|
+
// 1500+ chars). Passing it whole bloats the prompt and pulls a long
|
|
317
|
+
// answer that overruns the token budget, truncating the JSON.
|
|
318
|
+
const text = String(finding.finding || finding.text || finding);
|
|
319
|
+
return `${index + 1}. ${text.length > 300 ? text.slice(0, 300) + '…' : text}`;
|
|
320
|
+
})
|
|
313
321
|
.join('\n');
|
|
314
322
|
|
|
315
323
|
const prompt = `Research Topic: "${topic}"
|
|
@@ -337,20 +345,32 @@ Synthesize these findings into a comprehensive analysis:`;
|
|
|
337
345
|
required: ['summary', 'keyInsights', 'themes', 'confidence']
|
|
338
346
|
};
|
|
339
347
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
// Two attempts: small local models occasionally overrun the token
|
|
349
|
+
// budget mid-string, and a truncated response cannot be parsed. 1600
|
|
350
|
+
// tokens gives the brevity-capped answer ~2x headroom (800 truncated
|
|
351
|
+
// roughly two runs in three on real findings).
|
|
352
|
+
let lastError;
|
|
353
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
354
|
+
try {
|
|
355
|
+
const response = await this.generateCompletion(prompt, {
|
|
356
|
+
systemPrompt,
|
|
357
|
+
maxTokens: 1600,
|
|
358
|
+
temperature: 0.4,
|
|
359
|
+
format: synthesisSchema
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Strip markdown code fences if present
|
|
363
|
+
const cleaned = response.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '').trim();
|
|
364
|
+
const parsed = JSON.parse(cleaned);
|
|
365
|
+
if (!parsed || typeof parsed.summary !== 'string' || parsed.summary.length === 0) {
|
|
366
|
+
throw new Error('Synthesis response missing summary');
|
|
367
|
+
}
|
|
368
|
+
return parsed;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
lastError = error;
|
|
371
|
+
}
|
|
352
372
|
}
|
|
353
|
-
|
|
373
|
+
throw lastError;
|
|
354
374
|
} catch (error) {
|
|
355
375
|
this.logger.warn('LLM synthesis failed, using fallback', { error: error.message });
|
|
356
376
|
return this.fallbackSynthesis(findings, topic);
|