crawlforge-mcp-server 5.5.2 → 5.5.3
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": "5.5.
|
|
3
|
+
"version": "5.5.3",
|
|
4
4
|
"mcpName": "io.github.mysleekdesigns/crawlforge-mcp-server",
|
|
5
5
|
"description": "CrawlForge MCP Server - Professional Model Context Protocol server with 29 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
|
@@ -105,7 +105,7 @@ const taskStore = createTaskStore({ logger });
|
|
|
105
105
|
// Create the server
|
|
106
106
|
const server = new McpServer({
|
|
107
107
|
name: "crawlforge",
|
|
108
|
-
version: "5.5.
|
|
108
|
+
version: "5.5.3",
|
|
109
109
|
description: "Production-ready MCP server with 29 web scraping, crawling, and content processing tools. Features MCP Resources (crawlforge://), Prompts, Sampling fallback, Elicitation, stealth browsing, deep research, structured extraction, embedded JavaScript state 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.",
|
|
110
110
|
homepage: "https://www.crawlforge.dev",
|
|
111
111
|
icon: "https://www.crawlforge.dev/icon.png",
|
|
@@ -1412,7 +1412,13 @@ registerToolIfEnabled("stealth_mode", {
|
|
|
1412
1412
|
result = { disabled: true };
|
|
1413
1413
|
break;
|
|
1414
1414
|
case 'create_context': {
|
|
1415
|
-
|
|
1415
|
+
// Forward the tool-level engine the same way operation:"scrape" does —
|
|
1416
|
+
// without it, create_context always ran on chromium whatever the caller
|
|
1417
|
+
// asked for ("playwright" is this tool's public name for chromium).
|
|
1418
|
+
const contextData = await stealthBrowserManager.createStealthContext({
|
|
1419
|
+
...(stealthConfig || {}),
|
|
1420
|
+
engine: engine === 'camoufox' ? 'camoufox' : 'chromium'
|
|
1421
|
+
});
|
|
1416
1422
|
// The full fingerprint is ~4 KB of canvas noise arrays and WebGL
|
|
1417
1423
|
// extension lists no caller acts on. Summarise by default; verbose:true
|
|
1418
1424
|
// still returns all of it for debugging a detection failure.
|
|
@@ -434,9 +434,11 @@ export class StealthBrowserManager {
|
|
|
434
434
|
async createStealthContext(config = {}) {
|
|
435
435
|
const validatedConfig = StealthConfigSchema.parse({ ...this.defaultConfig, ...config });
|
|
436
436
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
437
|
+
// Always go through launchStealthBrowser: it returns the running browser
|
|
438
|
+
// when the engine matches, and closes + relaunches on an engine mismatch.
|
|
439
|
+
// Guarding on `!this.browser` here skipped that mismatch check, so a
|
|
440
|
+
// camoufox request silently reused an already-running chromium browser.
|
|
441
|
+
await this.launchStealthBrowser(validatedConfig);
|
|
440
442
|
|
|
441
443
|
// Generate fingerprint for this context
|
|
442
444
|
const fingerprint = this.generateAdvancedFingerprint(validatedConfig);
|
|
@@ -192,6 +192,14 @@ export class ExtractStructuredTool {
|
|
|
192
192
|
// the model was shown would null every correct price.
|
|
193
193
|
const fullSource = `${html}\n${textContent}`;
|
|
194
194
|
|
|
195
|
+
// A model that saw no answer sometimes fills a required field with a
|
|
196
|
+
// placeholder ("N/A", "unknown") instead of null. That carries no digits,
|
|
197
|
+
// so the provenance guard has nothing to check and the retry below never
|
|
198
|
+
// fired — git-scm.com is the case that found this: Readability drops the
|
|
199
|
+
// version box, the model answered "N/A", and the page plainly says 2.55.0.
|
|
200
|
+
const isPlaceholder = (v) => v == null ||
|
|
201
|
+
(typeof v === 'string' && (v.trim() === '' || /^(n\/?a|none|null|unknown|not (available|found|specified))$/i.test(v.trim())));
|
|
202
|
+
|
|
195
203
|
/** Run the guard over one extraction and describe what it did. */
|
|
196
204
|
const applyGuard = (result) => {
|
|
197
205
|
const checked = verifyNumericProvenance(result.data || {}, fullSource);
|
|
@@ -201,9 +209,14 @@ export class ExtractStructuredTool {
|
|
|
201
209
|
const nulledRequired = checked.unverified
|
|
202
210
|
.map((entry) => entry.path)
|
|
203
211
|
.filter((path) => (schema.required || []).includes(path));
|
|
212
|
+
// Retry currency: required fields with no usable answer, whether the
|
|
213
|
+
// guard nulled them or the model handed back a placeholder.
|
|
214
|
+
const missingRequired = (schema.required || [])
|
|
215
|
+
.filter((path) => nulledRequired.includes(path) || isPlaceholder(checked.data?.[path]));
|
|
204
216
|
return {
|
|
205
217
|
checked,
|
|
206
218
|
nulledRequired,
|
|
219
|
+
missingRequired,
|
|
207
220
|
result: {
|
|
208
221
|
...result,
|
|
209
222
|
data: checked.data,
|
|
@@ -234,7 +247,7 @@ export class ExtractStructuredTool {
|
|
|
234
247
|
// which is exactly what the guard will accept. Only a strictly better
|
|
235
248
|
// result is kept, so a retry can never make the answer worse.
|
|
236
249
|
const shownText = mainContentText($, html, url) || textContent;
|
|
237
|
-
if (guarded.
|
|
250
|
+
if (guarded.missingRequired.length > 0 && shownText !== textContent && textContent) {
|
|
238
251
|
try {
|
|
239
252
|
const llm = this._ensureLLMManager(llmConfig || {});
|
|
240
253
|
const retry = await llm.extractStructured(textContent, schema, {
|
|
@@ -243,7 +256,7 @@ export class ExtractStructuredTool {
|
|
|
243
256
|
});
|
|
244
257
|
if (retry?.method === 'llm') {
|
|
245
258
|
const retryGuarded = applyGuard(retry);
|
|
246
|
-
if (retryGuarded.
|
|
259
|
+
if (retryGuarded.missingRequired.length < guarded.missingRequired.length) {
|
|
247
260
|
guarded = retryGuarded;
|
|
248
261
|
}
|
|
249
262
|
}
|