crawlforge-mcp-server 5.2.4 → 5.2.5
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 +2 -2
- package/server.js +1 -1
- package/src/core/ChangeTracker.js +62 -4
- package/src/tools/tracking/trackChanges/index.js +1 -0
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.5
|
|
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.5",
|
|
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",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"cheerio": "^1.1.2",
|
|
114
114
|
"commander": "^14.0.3",
|
|
115
115
|
"compromise": "^14.14.4",
|
|
116
|
-
"crawlforge-extractors": "^1.2.
|
|
116
|
+
"crawlforge-extractors": "^1.2.1",
|
|
117
117
|
"diff": "^9.0.0",
|
|
118
118
|
"dotenv": "^17.2.1",
|
|
119
119
|
"franc": "^6.2.0",
|
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.5",
|
|
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",
|
|
@@ -312,14 +312,22 @@ export class ChangeTracker extends EventEmitter {
|
|
|
312
312
|
|
|
313
313
|
this.emit('changeDetected', changeRecord);
|
|
314
314
|
|
|
315
|
+
const ignoredOptions = this.findIgnoredCompareOptions(options, baseline.options);
|
|
316
|
+
|
|
315
317
|
return {
|
|
316
318
|
hasChanges: significance !== 'none',
|
|
317
319
|
significance,
|
|
318
320
|
changeType: changeRecord.changeType,
|
|
319
|
-
summary: this.generateChangeSummary(changeAnalysis),
|
|
321
|
+
summary: this.generateChangeSummary(changeAnalysis, significance),
|
|
320
322
|
details: changeAnalysis,
|
|
321
323
|
metrics: changeRecord.metrics,
|
|
322
|
-
recommendations: this.generateChangeRecommendations(changeRecord)
|
|
324
|
+
recommendations: this.generateChangeRecommendations(changeRecord),
|
|
325
|
+
...(ignoredOptions.length ? {
|
|
326
|
+
warnings: [
|
|
327
|
+
`${ignoredOptions.join(', ')} passed to this compare ${ignoredOptions.length === 1 ? 'was' : 'were'} ignored — ` +
|
|
328
|
+
`the baseline's options are applied to both sides of the diff. Recreate the baseline to change them.`
|
|
329
|
+
]
|
|
330
|
+
} : {})
|
|
323
331
|
};
|
|
324
332
|
|
|
325
333
|
} catch (error) {
|
|
@@ -328,6 +336,27 @@ export class ChangeTracker extends EventEmitter {
|
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Report analysis options supplied at compare time that differ from the
|
|
341
|
+
* baseline's and were therefore not applied.
|
|
342
|
+
*
|
|
343
|
+
* Ignoring them is deliberate — both sides of a diff have to be analyzed
|
|
344
|
+
* identically, and once customSelectors scoped the baseline it no longer
|
|
345
|
+
* holds the full document to re-scope. But doing it silently let a caller
|
|
346
|
+
* scope a compare and read the resulting whole-page churn as real change:
|
|
347
|
+
* the result is byte-identical to an unscoped run, with nothing saying so.
|
|
348
|
+
*
|
|
349
|
+
* @param {Object} callerOptions - trackingOptions passed to this compare
|
|
350
|
+
* @param {Object} baselineOptions - options stored with the baseline
|
|
351
|
+
* @returns {string[]} - names of the ignored options
|
|
352
|
+
*/
|
|
353
|
+
findIgnoredCompareOptions(callerOptions = {}, baselineOptions = {}) {
|
|
354
|
+
return ['granularity', 'customSelectors', 'excludeSelectors'].filter(key => {
|
|
355
|
+
if (callerOptions[key] === undefined) return false;
|
|
356
|
+
return JSON.stringify(callerOptions[key]) !== JSON.stringify(baselineOptions[key]);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
331
360
|
/**
|
|
332
361
|
* Analyze content structure and create hierarchical hashes
|
|
333
362
|
* @param {string} content - Content to analyze
|
|
@@ -701,6 +730,29 @@ export class ChangeTracker extends EventEmitter {
|
|
|
701
730
|
}
|
|
702
731
|
});
|
|
703
732
|
});
|
|
733
|
+
|
|
734
|
+
// Scoping to a tag outside that list (address, td, li, tr, dd) otherwise
|
|
735
|
+
// indexes ZERO elements: the scoped document is hashed, but nothing in it
|
|
736
|
+
// matches the allowlist, so every compare sees an empty element map and
|
|
737
|
+
// can never report an element-level change. Hash the scoped elements
|
|
738
|
+
// themselves for any tag the loop above does not already cover.
|
|
739
|
+
if (options.customSelectors?.length) {
|
|
740
|
+
const alreadyHashed = new Set(importantElements);
|
|
741
|
+
options.customSelectors.forEach((selector, selectorIndex) => {
|
|
742
|
+
$(selector).each((index, element) => {
|
|
743
|
+
const tag = element.tagName?.toLowerCase();
|
|
744
|
+
if (!tag || alreadyHashed.has(tag)) return;
|
|
745
|
+
|
|
746
|
+
const elementKey = `custom_${selectorIndex}_${index}`;
|
|
747
|
+
analysis.hashes.elements[elementKey] = this.hashContent($(element).html() || '');
|
|
748
|
+
|
|
749
|
+
if (options.trackAttributes) {
|
|
750
|
+
const attributes = element.attribs || {};
|
|
751
|
+
analysis.hashes.elements[`${elementKey}_attr`] = this.hashContent(JSON.stringify(attributes));
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
}
|
|
704
756
|
}
|
|
705
757
|
|
|
706
758
|
async analyzeTextLevel($, analysis, options) {
|
|
@@ -1160,7 +1212,7 @@ export class ChangeTracker extends EventEmitter {
|
|
|
1160
1212
|
return 'text_change';
|
|
1161
1213
|
}
|
|
1162
1214
|
|
|
1163
|
-
generateChangeSummary(changeAnalysis) {
|
|
1215
|
+
generateChangeSummary(changeAnalysis, significance) {
|
|
1164
1216
|
const { addedElements, removedElements, modifiedElements, similarity } = changeAnalysis;
|
|
1165
1217
|
|
|
1166
1218
|
const total = addedElements.length + removedElements.length + modifiedElements.length;
|
|
@@ -1171,7 +1223,13 @@ export class ChangeTracker extends EventEmitter {
|
|
|
1171
1223
|
added: addedElements.length,
|
|
1172
1224
|
removed: removedElements.length,
|
|
1173
1225
|
modified: modifiedElements.length,
|
|
1174
|
-
|
|
1226
|
+
// Sub-threshold text noise (a rotating session token, a base64 timestamp)
|
|
1227
|
+
// still lands in textChanges, so the description read "Text content
|
|
1228
|
+
// changed" on a compare that reported hasChanges:false and
|
|
1229
|
+
// totalChanges:0. Defer to the verdict the caller is given.
|
|
1230
|
+
changeDescription: significance === 'none'
|
|
1231
|
+
? 'No significant changes detected'
|
|
1232
|
+
: this.generateChangeDescription(changeAnalysis)
|
|
1175
1233
|
};
|
|
1176
1234
|
}
|
|
1177
1235
|
|
|
@@ -281,6 +281,7 @@ export class TrackChangesTool extends EventEmitter {
|
|
|
281
281
|
details: comparisonResult.details,
|
|
282
282
|
metrics: comparisonResult.metrics,
|
|
283
283
|
recommendations: comparisonResult.recommendations,
|
|
284
|
+
...(comparisonResult.warnings ? { warnings: comparisonResult.warnings } : {}),
|
|
284
285
|
snapshot: snapshotInfo, timestamp: Date.now()
|
|
285
286
|
};
|
|
286
287
|
}
|