crawlforge-mcp-server 6.22.1 → 6.22.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 +2 -2
- package/server.js +1 -1
- package/src/core/AgentOrchestrator.js +62 -9
- package/src/core/ResearchOrchestrator.js +21 -2
- package/src/core/research/claimFilters.js +20 -1
- package/src/server/inlineThreshold.js +4 -2
- package/src/skills/agent-skills/crawlforge-batch-automation/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-browser-sessions/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-change-tracking/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-deep-research/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-getting-started/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-stealth-browsing/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-structured-extraction/SKILL.md +1 -1
- package/src/skills/agent-skills/crawlforge-web-scraping/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlforge-mcp-server",
|
|
3
|
-
"version": "6.22.
|
|
3
|
+
"version": "6.22.3",
|
|
4
4
|
"mcpName": "io.github.mysleekdesigns/crawlforge-mcp-server",
|
|
5
5
|
"description": "CrawlForge MCP Server - Professional Model Context Protocol server with 31 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",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"cheerio": "^1.1.2",
|
|
122
122
|
"commander": "^14.0.3",
|
|
123
123
|
"compromise": "^14.14.4",
|
|
124
|
-
"crawlforge-extractors": "^1.18.
|
|
124
|
+
"crawlforge-extractors": "^1.18.2",
|
|
125
125
|
"diff": "^9.0.0",
|
|
126
126
|
"dotenv": "^17.2.1",
|
|
127
127
|
"franc": "^6.2.0",
|
package/server.js
CHANGED
|
@@ -113,7 +113,7 @@ if (configErrors.length > 0 && config.server.nodeEnv === 'production') {
|
|
|
113
113
|
// Create the server
|
|
114
114
|
const server = new McpServer({
|
|
115
115
|
name: "crawlforge",
|
|
116
|
-
version: "6.22.
|
|
116
|
+
version: "6.22.3",
|
|
117
117
|
description: "Production-ready MCP server with 31 web scraping, crawling, and content processing tools. Features MCP Resources (crawlforge://), Prompts, Sampling fallback, Elicitation, stealth browsing, stateful browser sessions with element refs, 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.",
|
|
118
118
|
homepage: "https://www.crawlforge.dev",
|
|
119
119
|
icon: "https://www.crawlforge.dev/icon.png",
|
|
@@ -161,13 +161,15 @@ export function unverifiedUrls(answer, citable, sourceText = '') {
|
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
/**
|
|
164
|
+
/** The http(s) links on a parsed page: absolute href and anchor text. */
|
|
165
165
|
function pageLinks($, baseUrl) {
|
|
166
166
|
const links = [];
|
|
167
167
|
$('a[href]').each((_, el) => {
|
|
168
168
|
try {
|
|
169
169
|
const url = new URL($(el).attr('href'), baseUrl);
|
|
170
|
-
if (url.protocol === 'http:' || url.protocol === 'https:')
|
|
170
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
171
|
+
links.push({ url: url.href, text: $(el).text().replace(/\s+/g, ' ').trim() });
|
|
172
|
+
}
|
|
171
173
|
} catch {
|
|
172
174
|
// An unparsable href cites nothing.
|
|
173
175
|
}
|
|
@@ -175,6 +177,38 @@ function pageLinks($, baseUrl) {
|
|
|
175
177
|
return links;
|
|
176
178
|
}
|
|
177
179
|
|
|
180
|
+
/** Most links one source lists under its text in the synthesis prompt. */
|
|
181
|
+
const SOURCE_LINKS_MAX = 8;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The links a source lists under its text in the synthesis prompt. Page text
|
|
185
|
+
* carries no link targets: the model could name the current HN top story but
|
|
186
|
+
* not link it, and cited an item URL from memory (R25 re-check, 2026-10-19).
|
|
187
|
+
* Listed: links whose anchor text is printed in the excerpt the model reads
|
|
188
|
+
* and is long enough to name something (three words: a title, not "97
|
|
189
|
+
* comments" or "next"), links to the page itself left out. Those whose anchor
|
|
190
|
+
* text carries one of the task's terms come first, then page order.
|
|
191
|
+
*
|
|
192
|
+
* @param {{url: string, text: string}[]} links
|
|
193
|
+
* @param {string} excerpt the source text the prompt carries
|
|
194
|
+
* @param {string} pageUrl
|
|
195
|
+
* @param {string[]} terms the task's terms, lowercase
|
|
196
|
+
* @returns {{url: string, text: string}[]}
|
|
197
|
+
*/
|
|
198
|
+
export function linksForSynthesis(links, excerpt, pageUrl, terms) {
|
|
199
|
+
const self = citationKey(pageUrl);
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
const listed = (links || []).filter(({ url, text }) => {
|
|
202
|
+
const key = citationKey(url);
|
|
203
|
+
if (!key || key === self || seen.has(key)) return false;
|
|
204
|
+
if (text.split(' ').length < 3 || !excerpt.includes(text)) return false;
|
|
205
|
+
seen.add(key);
|
|
206
|
+
return true;
|
|
207
|
+
});
|
|
208
|
+
const onTask = link => terms.some(t => link.text.toLowerCase().includes(t));
|
|
209
|
+
return [...listed.filter(onTask), ...listed.filter(l => !onTask(l))].slice(0, SOURCE_LINKS_MAX);
|
|
210
|
+
}
|
|
211
|
+
|
|
178
212
|
/**
|
|
179
213
|
* Current-state task gate: prompts about the live "now" ("right now",
|
|
180
214
|
* "current", "currently", "today", "latest", "#1 … now") go stale through search alone —
|
|
@@ -665,6 +699,8 @@ export class AgentOrchestrator {
|
|
|
665
699
|
// URLs an answer may cite: every evidence page's URL (requested and
|
|
666
700
|
// final) and the links on it.
|
|
667
701
|
const citable = [];
|
|
702
|
+
/** evidence url -> the links on that page, for the synthesis prompt. */
|
|
703
|
+
const linksOf = new Map();
|
|
668
704
|
let urlsFetched = 0;
|
|
669
705
|
let step = 0;
|
|
670
706
|
const warnings = [];
|
|
@@ -700,7 +736,9 @@ export class AgentOrchestrator {
|
|
|
700
736
|
text: truncate(plain.textContent),
|
|
701
737
|
step
|
|
702
738
|
});
|
|
703
|
-
|
|
739
|
+
const links = pageLinks(plain.$, plain.finalUrl);
|
|
740
|
+
linksOf.set(plain.finalUrl, links);
|
|
741
|
+
citable.push(url, plain.finalUrl, ...links.map(l => l.url));
|
|
704
742
|
continue;
|
|
705
743
|
}
|
|
706
744
|
// A real page that is simply off-topic: nothing to retry.
|
|
@@ -745,7 +783,9 @@ export class AgentOrchestrator {
|
|
|
745
783
|
via: 'stealth',
|
|
746
784
|
step
|
|
747
785
|
});
|
|
748
|
-
|
|
786
|
+
const links = pageLinks(load(stealth.html || ''), stealth.url || url);
|
|
787
|
+
linksOf.set(stealth.url || url, links);
|
|
788
|
+
citable.push(url, stealth.url || url, ...links.map(l => l.url));
|
|
749
789
|
continue;
|
|
750
790
|
}
|
|
751
791
|
warnings.push(`stealth retry of ${url} ${rendered.success ? 'returned a page unrelated to the task' : `did not get the page either${rendered.blocked?.vendor ? ` (${rendered.blocked.vendor})` : ''}`}`);
|
|
@@ -782,7 +822,12 @@ export class AgentOrchestrator {
|
|
|
782
822
|
const sliceTerms = promptTerms.map(t => t.replace(/[^\p{L}\p{N}]/gu, ''))
|
|
783
823
|
.filter(t => t.length > 3 && !/^(what|which|when|where|does|that|this|with|from|have|about)$/.test(t));
|
|
784
824
|
const combinedText = orderedEvidence
|
|
785
|
-
.map(e =>
|
|
825
|
+
.map(e => {
|
|
826
|
+
const excerpt = sliceForSynthesis(e.text, perSourceCap, sliceTerms);
|
|
827
|
+
const links = linksForSynthesis(linksOf.get(e.url), excerpt, e.url, sliceTerms);
|
|
828
|
+
return `--- Source: ${e.url}${e.snippet ? ' (search-result snippet; the page itself could not be fetched)' : ''} ---\n${excerpt}` +
|
|
829
|
+
(links.length > 0 ? `\nLinks on this page:\n${links.map(l => `- ${l.text}: ${l.url}`).join('\n')}` : '');
|
|
830
|
+
})
|
|
786
831
|
.join('\n\n');
|
|
787
832
|
|
|
788
833
|
if (!combinedText.trim()) {
|
|
@@ -880,7 +925,7 @@ export class AgentOrchestrator {
|
|
|
880
925
|
: '') +
|
|
881
926
|
`- Answer ONLY from the provided sources; do not use outside knowledge.\n` +
|
|
882
927
|
`- Read the sources carefully before concluding anything is missing from them.\n` +
|
|
883
|
-
`- Cite the exact
|
|
928
|
+
`- Cite the exact URL(s) you used: a source URL, or the URL an item has under "Links on this page".\n` +
|
|
884
929
|
`- If, after careful reading, the sources genuinely do not contain the answer, say so explicitly.\n` +
|
|
885
930
|
`- NEVER invent or guess a URL; cite only URLs that appear in the sources above.\n\n` +
|
|
886
931
|
`Provide a clear, concise answer.`;
|
|
@@ -971,14 +1016,22 @@ export class AgentOrchestrator {
|
|
|
971
1016
|
// it. R25 (2026-10-19) answered "not stated" over a promoted Wikipedia
|
|
972
1017
|
// Main Page, and the HN question from thehackernews.com, both with
|
|
973
1018
|
// degraded:false. The answer counts as the live page's when it cites
|
|
974
|
-
// that host
|
|
1019
|
+
// that host, states a value (version, date, number) the page shows, or
|
|
1020
|
+
// names an item the page links (anchor text of three words or more:
|
|
1021
|
+
// the HN title "DeepSeek just open-sourced its inference engine",
|
|
1022
|
+
// 2026-10-20). An invented URL on that host is no citation of it: it is
|
|
1023
|
+
// taken out before the host is looked for.
|
|
975
1024
|
if (currentState && promotedOrigin && !degraded) {
|
|
976
1025
|
const host = u => { try { return new URL(u).hostname.replace(/^www\./, ''); } catch { return null; } };
|
|
977
1026
|
const liveHost = host(promotedOrigin);
|
|
978
|
-
const
|
|
1027
|
+
const livePages = evidence.filter(e => host(e.url) === liveHost);
|
|
1028
|
+
const liveText = livePages.map(e => e.text || '').join('\n');
|
|
979
1029
|
// unverifiedValues against an empty text lists every value the answer states.
|
|
980
1030
|
const stated = unverifiedValues(answer, '');
|
|
981
|
-
const
|
|
1031
|
+
const cited = unverifiedLinks.reduce((text, url) => text.split(url).join(' '), answer).toLowerCase();
|
|
1032
|
+
const namesLiveItem = livePages.some(e => (linksOf.get(e.url) || [])
|
|
1033
|
+
.some(l => l.text.split(' ').length >= 3 && cited.includes(l.text.toLowerCase())));
|
|
1034
|
+
const fromLivePage = cited.includes(liveHost) || namesLiveItem ||
|
|
982
1035
|
(liveText !== '' && unverifiedValues(answer, liveText).length < stated.length);
|
|
983
1036
|
if (!fromLivePage) {
|
|
984
1037
|
degraded = true;
|
|
@@ -120,6 +120,25 @@ export function claimStatements(point) {
|
|
|
120
120
|
.filter(Boolean);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* The first `max` characters of a page's text, ended at the last sentence or
|
|
125
|
+
* line end inside them. A plain cut leaves the last sentence half written, and
|
|
126
|
+
* the summarizer took it as a key point: "…the same hostname can range from no
|
|
127
|
+
* bot pro" was a finding in the R25 re-check (2026-10-19). Cut as before when
|
|
128
|
+
* no end falls in the second half.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} text
|
|
131
|
+
* @param {number} max
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
export function cutAtSentence(text, max) {
|
|
135
|
+
if (text.length <= max) return text;
|
|
136
|
+
const cut = text.slice(0, max);
|
|
137
|
+
const ends = [...cut.matchAll(/[.!?]["'\u201D\u2019)]*(?=\s)|\n/g)];
|
|
138
|
+
const end = ends.length > 0 ? ends[ends.length - 1].index + ends[ends.length - 1][0].length : 0;
|
|
139
|
+
return end > max / 2 ? cut.slice(0, end).trimEnd() : cut;
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
/**
|
|
124
143
|
* Page text for the plain-fetch fallback, one line per block element. The
|
|
125
144
|
* regex tag-strip it replaces ran every block together on one line.
|
|
@@ -896,7 +915,7 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
896
915
|
const textContent = fallbackPageText(html);
|
|
897
916
|
if (textContent.length > 50) {
|
|
898
917
|
contentData = {
|
|
899
|
-
content: textContent
|
|
918
|
+
content: cutAtSentence(textContent, 5000),
|
|
900
919
|
metadata: { title: source.title || '' },
|
|
901
920
|
structuredData: {},
|
|
902
921
|
fallback: true
|
|
@@ -1425,7 +1444,7 @@ export class ResearchOrchestrator extends EventEmitter {
|
|
|
1425
1444
|
try {
|
|
1426
1445
|
if (!source.extractedContent) continue;
|
|
1427
1446
|
|
|
1428
|
-
const content = source.extractedContent
|
|
1447
|
+
const content = cutAtSentence(source.extractedContent, 5000); // Limit content length
|
|
1429
1448
|
|
|
1430
1449
|
// Use summarization to extract key points
|
|
1431
1450
|
const summary = await this.summarizeTool.execute({
|
|
@@ -40,6 +40,21 @@ const FRONT_MATTER_PATTERNS = [
|
|
|
40
40
|
/\bperformance (?:&|and) security by\b/i
|
|
41
41
|
];
|
|
42
42
|
|
|
43
|
+
// Lines that are not statements about the topic, all three key findings in the
|
|
44
|
+
// R25 re-check (2026-10-19, "What anti-bot systems do major websites use in
|
|
45
|
+
// 2026"): a question, which is an FAQ heading ("What makes a good anti-bot
|
|
46
|
+
// solution?"); an opening hook addressed to the reader ("If you've ever
|
|
47
|
+
// rage-clicked on a … puzzle, you already understand the problem with
|
|
48
|
+
// CAPTCHAs."); and a scoring rubric's weight ("Detection technology
|
|
49
|
+
// (signatures, ML, rules, session/event models): 25%"). Its live re-run added
|
|
50
|
+
// "What you are experiencing is the cat-and-mouse game…" and the signpost
|
|
51
|
+
// "Here's how Cloudflare, Akamai, Kasada, and PerimeterX each weigh…".
|
|
52
|
+
const NON_STATEMENT_PATTERNS = [
|
|
53
|
+
/\?["'”’)]*\s*$/,
|
|
54
|
+
/^(?:if you(?:['’]ve| have) ever|have you ever|ever (?:wondered|tried|noticed)|imagine|picture this|let['’]s|what you(?:['’]re| are)|here['’]s (?:how|what|why|where))\b/i,
|
|
55
|
+
/^[^.!?]{2,150}:\s*\d{1,3}(?:\.\d+)?\s*%\.?$/
|
|
56
|
+
];
|
|
57
|
+
|
|
43
58
|
// Tokens that mark an affiliation line rather than a sentence about the topic.
|
|
44
59
|
const AFFILIATION_TOKENS = new Set([
|
|
45
60
|
'university', 'universities', 'institute', 'institut', 'department', 'dept',
|
|
@@ -159,10 +174,14 @@ export function properNounDensity(text) {
|
|
|
159
174
|
// author or affiliation block rather than a sentence about the topic.
|
|
160
175
|
const PROPER_NOUN_DENSITY_MAX = 0.5;
|
|
161
176
|
|
|
162
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* A candidate claim is admissible when it is a verb-bearing, non-front-matter
|
|
179
|
+
* statement: not a question, a hook addressed to the reader, or a rubric weight.
|
|
180
|
+
*/
|
|
163
181
|
export function isAdmissibleClaim(text) {
|
|
164
182
|
if (typeof text !== 'string' || text.trim().length === 0) return false;
|
|
165
183
|
if (isFrontMatter(text)) return false;
|
|
184
|
+
if (NON_STATEMENT_PATTERNS.some(pattern => pattern.test(text.trim()))) return false;
|
|
166
185
|
if (!hasFiniteVerb(text)) return false;
|
|
167
186
|
return properNounDensity(text) <= PROPER_NOUN_DENSITY_MAX;
|
|
168
187
|
}
|
|
@@ -84,8 +84,10 @@ export const INLINE_THRESHOLD_TOOLS = Object.freeze({
|
|
|
84
84
|
scrape_template: { textPaths: [], truncate: true },
|
|
85
85
|
map_site: { textPaths: [], truncate: true, keep: ['site_map', 'statistics'] },
|
|
86
86
|
// A prose answer is the text view; a structured or missing one leaves the
|
|
87
|
-
// JSON view, and the sources stay inline when they fit.
|
|
88
|
-
|
|
87
|
+
// JSON view, and the sources stay inline when they fit. `reason` too: a
|
|
88
|
+
// degraded run's reason runs past the 200 characters kept unasked, and a
|
|
89
|
+
// degraded:true with no reason says nothing (R25 follow-up, 2026-10-20).
|
|
90
|
+
agent: { textPaths: ['answer'], truncate: true, keep: ['reason', 'evidence', 'search_results', 'provenance'] },
|
|
89
91
|
// llms-full.txt is the large file and the view; llms.txt stays inline under
|
|
90
92
|
// `files` when it fits. Neither key can be named in a json_path (the "."),
|
|
91
93
|
// so the view is the way to read the long one.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-batch-automation
|
|
3
3
|
description: "Automates large scraping jobs and browser interactions with CrawlForge's batch_scrape, get_batch_results, scrape_with_actions, and generate_llms_txt tools. Use when the user wants to scrape many URLs at once, batch-scrape a list of pages, collect dozens of product, news, or competitor pages, run browser actions (click, type, scroll, wait) before scraping, log in or fill a form before extracting, or generate an llms.txt for a site. Use sync mode for up to about 25 URLs and async with a webhook for large batches; retrieve paginated output with get_batch_results."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-browser-sessions
|
|
3
3
|
description: "Keeps one browser page alive across several tool calls with CrawlForge's browser_session tool. Use when the user needs to log in and then read pages behind that login, work through a multi-step flow (search, filter, paginate, fill a wizard), or look at a page before deciding what to click. The loop is: open a session on a URL, snapshot it to list the interactive elements with stable refs (@e1, @e2), act on those refs, read the content, close. Unlike scrape_with_actions, which is one-shot and closes its browser when the call returns, a session survives between calls, so one login is paid for once and a wrong selector costs one call instead of a whole chain."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-change-tracking
|
|
3
3
|
description: "Monitors web pages for changes over time with CrawlForge's track_changes tool. Use when the user wants to track changes to a page, watch a URL, monitor competitor pricing, detect when content updates, get notified of regulation or product-availability changes, or diff a page against a saved baseline. Workflow: create a baseline with operation create_baseline, then periodically compare with operation compare to get a change percentage and a diff; supports CSS-selector scoping, webhooks, and scheduled monitoring."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-deep-research
|
|
3
3
|
description: "Runs multi-source web research and autonomous question-answering with CrawlForge's deep_research, agent, search_web, and reddit_search tools. Use when the user wants to research a topic, do a deep dive, compare competitors, gather facts with citations, answer a question from the web, search the web, or get a synthesized report from many sources, or search Reddit posts, comments, and threads. deep_research expands queries, fetches and dedupes sources, then synthesizes; agent autonomously plans searches and navigation from a plain-English prompt with no URLs needed; search_web returns ranked results; reddit_search searches Reddit via community archives (reddit.com blocks scrapers). Caps costs via max_urls and confirms before expensive runs."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-getting-started
|
|
3
3
|
description: "Orientation and tool-selection guide for the CrawlForge MCP server's 31 web tools. Use when the user is getting started with CrawlForge, asks which CrawlForge tool to use, how to set up the API key, how skills or the CLI work, what a tool costs in credits, or when one tool fails and a fallback is needed. Routes requests to the right specialized skill (web scraping, deep research, stealth, structured extraction, change tracking, batch automation, browser sessions), and explains MCP-tools-vs-CLI, the Ollama-first LLM fallback chain, and per-tool credit costs."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-stealth-browsing
|
|
3
3
|
description: "Bypasses bot detection with CrawlForge's stealth_mode tool and looks up a country's locale settings with localization. Use when a site returns 403 or 429, CAPTCHAs, 'please enable JavaScript', or empty content, or is protected by Cloudflare, DataDome, or PerimeterX, or when the user needs a page in a specific locale, language, or timezone. stealth_mode runs a stealth browser (engine auto by default: camoufox/Firefox for advanced fingerprinting, playwright/Chromium when pinned for speed) and can screenshot; localization returns a country's Accept-Language, timezone and currency for you to pass to fetch_url or stealth_mode - it applies nothing itself and uses no proxy. Explains when to escalate from a normal scrape to stealth, and why a hard block needs a residential proxy."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-structured-extraction
|
|
3
3
|
description: "Extracts structured JSON and analyzes content with CrawlForge's extract_structured, extract_with_llm, extract_embedded_state, scrape_structured, scrape_template, process_document, analyze_content, summarize_content, and list_ollama_models tools. Use when the user wants to extract specific fields, pull data into a JSON schema, extract by natural-language prompt, scrape with CSS selectors, read a page's embedded JavaScript state (__NEXT_DATA__, React Server Components, Nuxt, Apollo, Redux), get product, profile, or repo data from known sites (Amazon, LinkedIn, GitHub, YouTube, Reddit, and more), parse a PDF or DOCX, summarize a page, or analyze sentiment, entities, or keywords. Defaults to local Ollama for LLM extraction; OpenAI and Anthropic optional."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: crawlforge-web-scraping
|
|
3
3
|
description: "Scrapes web pages and returns clean Markdown, HTML, plain text, links, or metadata using CrawlForge's scrape, fetch_url, extract_content, extract_text, extract_links, extract_metadata, map_site, and crawl_deep tools. Use when the user wants to scrape a URL, fetch a page, get the markdown or text of a website, extract links or metadata, read an article, map a site's URLs, generate a sitemap, or crawl a whole website or documentation site. Prefer the unified scrape tool to get multiple formats in one call; use map_site to discover URLs and crawl_deep to walk an entire site."
|
|
4
4
|
metadata:
|
|
5
|
-
version: 6.22.
|
|
5
|
+
version: 6.22.3
|
|
6
6
|
source: crawlforge-mcp-server
|
|
7
7
|
---
|
|
8
8
|
|