@webcrawl/mcp 1.4.2 → 1.4.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.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +5 -7
  3. package/dist/core/engine/awswaf.d.ts +59 -0
  4. package/dist/core/engine/awswaf.js +164 -0
  5. package/dist/core/engine/cache.d.ts +70 -0
  6. package/dist/core/engine/cache.js +297 -0
  7. package/dist/core/engine/chrome.d.ts +14 -0
  8. package/dist/core/engine/chrome.js +140 -0
  9. package/dist/core/engine/crawl-policy.d.ts +36 -0
  10. package/dist/core/engine/crawl-policy.js +134 -0
  11. package/dist/core/engine/crawler.d.ts +152 -0
  12. package/dist/core/engine/crawler.js +918 -0
  13. package/dist/core/engine/fetcher.d.ts +92 -0
  14. package/dist/core/engine/fetcher.js +494 -0
  15. package/dist/core/engine/index.d.ts +7 -0
  16. package/dist/core/engine/index.js +7 -0
  17. package/dist/core/engine/markdown.d.ts +33 -0
  18. package/dist/core/engine/markdown.js +334 -0
  19. package/dist/core/engine/path-scope.d.ts +21 -0
  20. package/dist/core/engine/path-scope.js +88 -0
  21. package/dist/core/engine/render.d.ts +41 -0
  22. package/dist/core/engine/render.js +138 -0
  23. package/dist/core/engine/safe-url.d.ts +29 -0
  24. package/dist/core/engine/safe-url.js +109 -0
  25. package/dist/core/engine/session.d.ts +93 -0
  26. package/dist/core/engine/session.js +250 -0
  27. package/dist/core/engine/stealth.d.ts +83 -0
  28. package/dist/core/engine/stealth.js +222 -0
  29. package/dist/core/engine.d.ts +1 -0
  30. package/dist/core/engine.js +1 -0
  31. package/dist/core/extract/business.d.ts +28 -0
  32. package/dist/core/extract/business.js +344 -0
  33. package/dist/core/extract/currency.d.ts +15 -0
  34. package/dist/core/extract/currency.js +259 -0
  35. package/dist/core/extract/cve-cache.d.ts +62 -0
  36. package/dist/core/extract/cve-cache.js +277 -0
  37. package/dist/core/extract/cve.d.ts +57 -0
  38. package/dist/core/extract/cve.js +397 -0
  39. package/dist/core/extract/dedupe-key.d.ts +12 -0
  40. package/dist/core/extract/dedupe-key.js +36 -0
  41. package/dist/core/extract/flights.d.ts +37 -0
  42. package/dist/core/extract/flights.js +373 -0
  43. package/dist/core/extract/hotels.d.ts +35 -0
  44. package/dist/core/extract/hotels.js +473 -0
  45. package/dist/core/extract/index.d.ts +9 -0
  46. package/dist/core/extract/index.js +9 -0
  47. package/dist/core/extract/real-estate.d.ts +53 -0
  48. package/dist/core/extract/real-estate.js +835 -0
  49. package/dist/core/extract/scaledown.d.ts +14 -0
  50. package/dist/core/extract/scaledown.js +484 -0
  51. package/dist/core/extract/shopping.d.ts +44 -0
  52. package/dist/core/extract/shopping.js +393 -0
  53. package/dist/core/extract/structured.d.ts +14 -0
  54. package/dist/core/extract/structured.js +144 -0
  55. package/dist/core/image-url.d.ts +5 -0
  56. package/dist/core/image-url.js +13 -0
  57. package/dist/core/interact/driver.d.ts +72 -0
  58. package/dist/core/interact/driver.js +384 -0
  59. package/dist/core/interact/remote.d.ts +41 -0
  60. package/dist/core/interact/remote.js +133 -0
  61. package/dist/core/interact/spans.d.ts +46 -0
  62. package/dist/core/interact/spans.js +32 -0
  63. package/dist/core/interact/validate.d.ts +34 -0
  64. package/dist/core/interact/validate.js +135 -0
  65. package/dist/core/interact/vm.d.ts +43 -0
  66. package/dist/core/interact/vm.js +338 -0
  67. package/dist/core/local-engine.d.ts +56 -0
  68. package/dist/core/local-engine.js +667 -0
  69. package/dist/core/parse/detect.d.ts +29 -0
  70. package/dist/core/parse/detect.js +152 -0
  71. package/dist/core/parse/fetch.d.ts +20 -0
  72. package/dist/core/parse/fetch.js +39 -0
  73. package/dist/core/parse/image.d.ts +13 -0
  74. package/dist/core/parse/image.js +100 -0
  75. package/dist/core/parse/index.d.ts +53 -0
  76. package/dist/core/parse/index.js +153 -0
  77. package/dist/core/parse/limits.d.ts +12 -0
  78. package/dist/core/parse/limits.js +29 -0
  79. package/dist/core/parse/media.d.ts +42 -0
  80. package/dist/core/parse/media.js +238 -0
  81. package/dist/core/parse/merge.d.ts +19 -0
  82. package/dist/core/parse/merge.js +48 -0
  83. package/dist/core/parse/ocr.d.ts +25 -0
  84. package/dist/core/parse/ocr.js +121 -0
  85. package/dist/core/parse/office.d.ts +28 -0
  86. package/dist/core/parse/office.js +83 -0
  87. package/dist/core/parse/pdf.d.ts +27 -0
  88. package/dist/core/parse/pdf.js +156 -0
  89. package/dist/core/parse/types.d.ts +38 -0
  90. package/dist/core/parse/types.js +1 -0
  91. package/dist/core/parse/youtube.d.ts +30 -0
  92. package/dist/core/parse/youtube.js +264 -0
  93. package/dist/core/ranker/decision.d.ts +28 -0
  94. package/dist/core/ranker/decision.js +151 -0
  95. package/dist/core/ranker/index.d.ts +5 -0
  96. package/dist/core/ranker/index.js +361 -0
  97. package/dist/core/ranker/lexical.d.ts +82 -0
  98. package/dist/core/ranker/lexical.js +643 -0
  99. package/dist/core/ranker/types.d.ts +90 -0
  100. package/dist/core/ranker/types.js +1 -0
  101. package/dist/core/ranker.d.ts +1 -0
  102. package/dist/core/ranker.js +1 -0
  103. package/dist/core/sandbox.d.ts +29 -0
  104. package/dist/core/sandbox.js +222 -0
  105. package/dist/core/search/catalog.d.ts +176 -0
  106. package/dist/core/search/catalog.js +488 -0
  107. package/dist/core/search/index.d.ts +3 -0
  108. package/dist/core/search/index.js +3 -0
  109. package/dist/core/search/pipeline.d.ts +7 -0
  110. package/dist/core/search/pipeline.js +719 -0
  111. package/dist/core/search/providers.d.ts +72 -0
  112. package/dist/core/search/providers.js +1270 -0
  113. package/dist/core/search.d.ts +1 -0
  114. package/dist/core/search.js +1 -0
  115. package/dist/core/targets/developer.d.ts +44 -0
  116. package/dist/core/targets/developer.js +338 -0
  117. package/dist/core/targets/index.d.ts +3 -0
  118. package/dist/core/targets/index.js +3 -0
  119. package/dist/core/targets/registry.d.ts +32 -0
  120. package/dist/core/targets/registry.js +411 -0
  121. package/dist/core/targets/types.d.ts +72 -0
  122. package/dist/core/targets/types.js +8 -0
  123. package/dist/core/targets.d.ts +1 -0
  124. package/dist/core/targets.js +1 -0
  125. package/dist/format.js +5 -1
  126. package/dist/index.d.ts +1 -0
  127. package/dist/index.js +1 -0
  128. package/dist/load-local-engine.d.ts +7 -0
  129. package/dist/load-local-engine.js +22 -0
  130. package/dist/server.js +3 -0
  131. package/package.json +11 -3
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Unified Target & Extractor Registry for Webcrawl Anything.
3
+ *
4
+ * Wraps every built-in vertical extractor (`src/extract/*`), binary/media parser (`src/parse/*`),
5
+ * multi-tier web crawler (`src/engine/*`), and developer target (`src/targets/developer.ts`)
6
+ * behind a single discoverable interface with REA-style Evidence & Provenance.
7
+ */
8
+ import crypto from "node:crypto";
9
+ import { extractBusinessListingsFromHtml, extractCveIdsFromText, extractFlightOffersFromHtml, extractHotelListingsFromHtml, extractOffersFromHtml, extractPropertyListingsFromHtml, groupOffersIntoProducts, } from "../extract/index.js";
10
+ import { fastHtmlToMarkdown } from "../engine/markdown.js";
11
+ import { BINARY_KINDS, detectFileKind } from "../parse/detect.js";
12
+ import { youtubeVideoId } from "../parse/youtube.js";
13
+ import { arxivTargetPlugin, githubTargetPlugin, llmsTxtTargetPlugin, } from "./developer.js";
14
+ function sha256(content) {
15
+ return crypto.createHash("sha256").update(content).digest("hex");
16
+ }
17
+ export const webSpaTargetPlugin = {
18
+ id: "web-spa",
19
+ name: "Websites, SPAs & Anti-Bot Pages",
20
+ category: "web",
21
+ returns: "Clean Markdown, title, outbound links, JSON-LD / OpenGraph metadata, and tier provenance",
22
+ requirements: "Built-in impit TLS impersonation; optional local Chrome/Playwright or webcrawl.app cloud stealth for Cloudflare/DataDome",
23
+ examples: [
24
+ "npx webcrawl scrape https://news.ycombinator.com",
25
+ "npx webcrawl crawl https://docs.stripe.com -n 10",
26
+ ],
27
+ matches({ url }) {
28
+ if (url && /^https?:\/\//i.test(url)) {
29
+ return { matched: true, confidence: 0.5, reason: "Generic HTTP/HTTPS web URL" };
30
+ }
31
+ return { matched: false, confidence: 0 };
32
+ },
33
+ extract(input) {
34
+ const html = input.html ?? "";
35
+ const url = input.url ?? "https://example.com";
36
+ const { title, markdown, links } = fastHtmlToMarkdown(html, url);
37
+ const matchedSignals = ["html:fast-markdown"];
38
+ const limitations = [];
39
+ if (!markdown.trim()) {
40
+ limitations.push("Static HTML contained no readable body text; enable browser/stealth escalation for client-rendered SPA");
41
+ }
42
+ return {
43
+ targetId: "web-spa",
44
+ targetName: "Websites, SPAs & Anti-Bot Pages",
45
+ category: "web",
46
+ title,
47
+ markdown,
48
+ structured: { title, linkCount: links.length, links: links.slice(0, 50) },
49
+ evidence: {
50
+ targetId: "web-spa",
51
+ tier: "static",
52
+ contentHash: sha256(html),
53
+ sourceUrl: url,
54
+ matchedSignals,
55
+ limitations,
56
+ },
57
+ };
58
+ },
59
+ };
60
+ export const pdfOfficeTargetPlugin = {
61
+ id: "pdf-office",
62
+ name: "PDFs & Office Documents (DOCX, XLSX, PPTX)",
63
+ category: "documents",
64
+ returns: "Page-numbered Markdown, tables, native text extraction, automatic OCR on scanned pages, and skipped-page diagnostics",
65
+ requirements: "Built-in @firecrawl/pdf-inspector, mammoth, and officeparser; optional OCR provider for scanned pages",
66
+ examples: [
67
+ "npx webcrawl scrape https://arxiv.org/pdf/1706.03762.pdf",
68
+ ],
69
+ matches({ url, contentType }) {
70
+ if (url || contentType) {
71
+ const { kind } = detectFileKind({ contentType, fileName: url });
72
+ if (kind === "pdf" || kind === "docx" || kind === "xlsx" || kind === "pptx") {
73
+ return { matched: true, confidence: 0.99, reason: `Detected ${kind.toUpperCase()} document` };
74
+ }
75
+ }
76
+ return { matched: false, confidence: 0 };
77
+ },
78
+ };
79
+ export const youtubeMediaTargetPlugin = {
80
+ id: "youtube-media",
81
+ name: "YouTube Videos, Audio & Media Files",
82
+ category: "media",
83
+ returns: "Timestamped transcript Markdown, video title/channel/duration metadata, and spoken audio transcription",
84
+ requirements: "Built-in zero-key YouTube multi-client caption extractor; optional audio transcription provider for raw MP3/MP4",
85
+ examples: [
86
+ "npx webcrawl scrape https://www.youtube.com/watch?v=dQw4w9WgXcQ",
87
+ ],
88
+ matches({ url, contentType }) {
89
+ if (url && youtubeVideoId(url)) {
90
+ return { matched: true, confidence: 0.99, reason: "YouTube video URL" };
91
+ }
92
+ if (url || contentType) {
93
+ const { kind } = detectFileKind({ contentType, fileName: url });
94
+ if (kind === "audio" || kind === "video" || (BINARY_KINDS.has(kind) && kind === "image")) {
95
+ return { matched: true, confidence: 0.95, reason: `Detected ${kind} media resource` };
96
+ }
97
+ }
98
+ return { matched: false, confidence: 0 };
99
+ },
100
+ };
101
+ export const shoppingTargetPlugin = {
102
+ id: "shopping",
103
+ name: "E-Commerce Products & Multi-Retailer Offers",
104
+ category: "commerce",
105
+ returns: "Normalized prices, currency, stock availability, seller ratings, free-shipping flags, and grouped product comparisons",
106
+ requirements: "Built-in JSON-LD Product/Offer, OpenGraph, and SERP shopping card extractors",
107
+ examples: [
108
+ "npx webcrawl search \"Sony WH-1000XM5 headphones\" -c shopping",
109
+ ],
110
+ matches({ category, query, url }) {
111
+ if (category === "shopping") {
112
+ return { matched: true, confidence: 0.99, reason: "Shopping search category" };
113
+ }
114
+ if (url && /\b(amazon\.|ebay\.|walmart\.|bestbuy\.|jbhifi\.|shopify)/i.test(url)) {
115
+ return { matched: true, confidence: 0.85, reason: "Retailer product domain" };
116
+ }
117
+ if (query && /\b(buy|price|cheapest|deal|in stock|review)\b/i.test(query)) {
118
+ return { matched: true, confidence: 0.65, reason: "Commercial shopping query intent" };
119
+ }
120
+ return { matched: false, confidence: 0 };
121
+ },
122
+ extract(input) {
123
+ const html = input.html ?? "";
124
+ const url = input.url ?? "https://example.com";
125
+ const offers = extractOffersFromHtml(html, url, input.countryCode);
126
+ const products = groupOffersIntoProducts(offers, input.query ?? "");
127
+ const matchedSignals = offers.map((o) => `offer:${o.source}`);
128
+ const limitations = offers.length === 0 ? ["No JSON-LD Product/Offer or OpenGraph price tags found on page"] : [];
129
+ return {
130
+ targetId: "shopping",
131
+ targetName: "E-Commerce Products & Multi-Retailer Offers",
132
+ category: "commerce",
133
+ structured: { offers, products },
134
+ evidence: {
135
+ targetId: "shopping",
136
+ tier: "static",
137
+ contentHash: sha256(html),
138
+ sourceUrl: url,
139
+ matchedSignals: Array.from(new Set(matchedSignals)),
140
+ limitations,
141
+ },
142
+ };
143
+ },
144
+ };
145
+ export const flightsTargetPlugin = {
146
+ id: "flights",
147
+ name: "Airline Routes, Fares & Schedules",
148
+ category: "travel",
149
+ returns: "Origin/destination IATA codes, airline, price, cabin class, stops, duration, and trip type",
150
+ requirements: "Built-in IATA route parser, JSON-LD FlightReservation, and fare table extractor",
151
+ examples: [
152
+ "npx webcrawl search \"flights SYD to SFO non-stop\" -c flights",
153
+ ],
154
+ matches({ category, query }) {
155
+ if (category === "flights") {
156
+ return { matched: true, confidence: 0.99, reason: "Flights search category" };
157
+ }
158
+ if (query && /\b(flights?|airfare|non-stop|round-trip|[A-Z]{3}\s+to\s+[A-Z]{3})\b/i.test(query)) {
159
+ return { matched: true, confidence: 0.8, reason: "Flight route query intent" };
160
+ }
161
+ return { matched: false, confidence: 0 };
162
+ },
163
+ extract(input) {
164
+ const html = input.html ?? "";
165
+ const url = input.url ?? "https://example.com";
166
+ const flights = extractFlightOffersFromHtml(html, url, input.countryCode);
167
+ return {
168
+ targetId: "flights",
169
+ targetName: "Airline Routes, Fares & Schedules",
170
+ category: "travel",
171
+ structured: { flights },
172
+ evidence: {
173
+ targetId: "flights",
174
+ tier: "static",
175
+ contentHash: sha256(html),
176
+ sourceUrl: url,
177
+ matchedSignals: flights.length > 0 ? ["dom:flight-offers"] : [],
178
+ limitations: flights.length === 0 ? ["No structured flight fares found in static HTML"] : [],
179
+ },
180
+ };
181
+ },
182
+ };
183
+ export const hotelsTargetPlugin = {
184
+ id: "hotels",
185
+ name: "Hotels, Stays & Nightly Rates",
186
+ category: "travel",
187
+ returns: "Hotel name, nightly rate, star rating, guest review score, neighbourhood, and amenities",
188
+ requirements: "Built-in LodgingBusiness / Hotel JSON-LD and booking card extractor",
189
+ examples: [
190
+ "npx webcrawl search \"boutique hotels in Tokyo Shinjuku\" -c hotels",
191
+ ],
192
+ matches({ category, query, url }) {
193
+ if (category === "hotels") {
194
+ return { matched: true, confidence: 0.99, reason: "Hotels search category" };
195
+ }
196
+ if (url && /\b(booking\.com|expedia\.|hotels\.com|airbnb\.)/i.test(url)) {
197
+ return { matched: true, confidence: 0.85, reason: "Hospitality domain" };
198
+ }
199
+ if (query && /\b(hotels?|resort|nightly rate|check-in|accommodation)\b/i.test(query)) {
200
+ return { matched: true, confidence: 0.75, reason: "Hotel search query intent" };
201
+ }
202
+ return { matched: false, confidence: 0 };
203
+ },
204
+ extract(input) {
205
+ const html = input.html ?? "";
206
+ const url = input.url ?? "https://example.com";
207
+ const hotels = extractHotelListingsFromHtml(html, url, input.countryCode);
208
+ return {
209
+ targetId: "hotels",
210
+ targetName: "Hotels, Stays & Nightly Rates",
211
+ category: "travel",
212
+ structured: { hotels },
213
+ evidence: {
214
+ targetId: "hotels",
215
+ tier: "static",
216
+ contentHash: sha256(html),
217
+ sourceUrl: url,
218
+ matchedSignals: hotels.length > 0 ? ["dom:hotel-listings"] : [],
219
+ limitations: hotels.length === 0 ? ["No structured hotel listings found in static HTML"] : [],
220
+ },
221
+ };
222
+ },
223
+ };
224
+ export const realEstateTargetPlugin = {
225
+ id: "real-estate",
226
+ name: "Real Estate & Property Listings",
227
+ category: "real-estate",
228
+ returns: "Property address, price/rent period, bedrooms, bathrooms, parking, land size, and listing type (sale/rent/sold)",
229
+ requirements: "Built-in RealEstateListing / Residence JSON-LD and property portal DOM extractor",
230
+ examples: [
231
+ "npx webcrawl search \"3 bedroom house in Austin TX\" -c real-estate --sub buy",
232
+ ],
233
+ matches({ category, query, url }) {
234
+ if (category === "real-estate") {
235
+ return { matched: true, confidence: 0.99, reason: "Real estate search category" };
236
+ }
237
+ if (url && /\b(zillow\.com|realtor\.com|redfin\.com|domain\.com\.au|realestate\.com\.au|rightmove\.co\.uk)/i.test(url)) {
238
+ return { matched: true, confidence: 0.9, reason: "Real estate portal domain" };
239
+ }
240
+ if (query && /\b(for sale|for rent|bedrooms?| townhouses?|apartments?)\b/i.test(query)) {
241
+ return { matched: true, confidence: 0.7, reason: "Real estate query intent" };
242
+ }
243
+ return { matched: false, confidence: 0 };
244
+ },
245
+ extract(input) {
246
+ const html = input.html ?? "";
247
+ const url = input.url ?? "https://example.com";
248
+ const listings = extractPropertyListingsFromHtml(html, url, input.countryCode);
249
+ return {
250
+ targetId: "real-estate",
251
+ targetName: "Real Estate & Property Listings",
252
+ category: "real-estate",
253
+ structured: { listings },
254
+ evidence: {
255
+ targetId: "real-estate",
256
+ tier: "static",
257
+ contentHash: sha256(html),
258
+ sourceUrl: url,
259
+ matchedSignals: listings.length > 0 ? ["dom:property-listings"] : [],
260
+ limitations: listings.length === 0 ? ["No structured property cards found in static HTML"] : [],
261
+ },
262
+ };
263
+ },
264
+ };
265
+ export const localBusinessTargetPlugin = {
266
+ id: "local-business",
267
+ name: "Local Businesses, Clinics & Services",
268
+ category: "local",
269
+ returns: "Business name, phone, street address, opening hours, rating, review count, and price range",
270
+ requirements: "Built-in LocalBusiness schema.org & contact block extractor",
271
+ examples: [
272
+ "npx webcrawl search \"24 hour emergency vet in Melbourne\" -c business",
273
+ ],
274
+ matches({ category }) {
275
+ if (category === "business") {
276
+ return { matched: true, confidence: 0.99, reason: "Local business search category" };
277
+ }
278
+ return { matched: false, confidence: 0 };
279
+ },
280
+ extract(input) {
281
+ const html = input.html ?? "";
282
+ const url = input.url ?? "https://example.com";
283
+ const businesses = extractBusinessListingsFromHtml(html, url);
284
+ return {
285
+ targetId: "local-business",
286
+ targetName: "Local Businesses, Clinics & Services",
287
+ category: "local",
288
+ structured: { businesses },
289
+ evidence: {
290
+ targetId: "local-business",
291
+ tier: "static",
292
+ contentHash: sha256(html),
293
+ sourceUrl: url,
294
+ matchedSignals: businesses.length > 0 ? ["dom:local-business"] : [],
295
+ limitations: businesses.length === 0 ? ["No LocalBusiness JSON-LD or contact card found"] : [],
296
+ },
297
+ };
298
+ },
299
+ };
300
+ export const cveSecurityTargetPlugin = {
301
+ id: "cve-security",
302
+ name: "CVEs, GHSA Advisories & Exploit Catalogs",
303
+ category: "security",
304
+ returns: "CVE/GHSA IDs, CVSS v3/v4 score & vector, severity, CWEs, affected package ranges, fixed versions, and CISA KEV status",
305
+ requirements: "Built-in NVD 2.0 + OSV.dev advisory correlator and page vulnerability parser (zero API key required)",
306
+ examples: [
307
+ "npx webcrawl search \"CVE-2024-3094 xz utils\" -c security",
308
+ "npx webcrawl search \"nextjs middleware auth bypass cve\" -c security",
309
+ ],
310
+ matches({ category, query, url }) {
311
+ if (category === "security") {
312
+ return { matched: true, confidence: 0.99, reason: "Security search category" };
313
+ }
314
+ const text = `${url ?? ""} ${query ?? ""}`;
315
+ if (/\b(CVE-\d{4}-\d{4,7}|GHSA-[23456789cfghjmpqrvwx]{4}-[23456789cfghjmpqrvwx]{4}-[23456789cfghjmpqrvwx]{4})\b/i.test(text)) {
316
+ return { matched: true, confidence: 0.98, reason: "CVE or GHSA identifier detected" };
317
+ }
318
+ return { matched: false, confidence: 0 };
319
+ },
320
+ extract(input) {
321
+ const text = input.markdown ?? input.html ?? input.query ?? "";
322
+ const cveIds = extractCveIdsFromText(text);
323
+ return {
324
+ targetId: "cve-security",
325
+ targetName: "CVEs, GHSA Advisories & Exploit Catalogs",
326
+ category: "security",
327
+ structured: { cveIds },
328
+ evidence: {
329
+ targetId: "cve-security",
330
+ tier: "api",
331
+ contentHash: sha256(text),
332
+ sourceUrl: input.url,
333
+ matchedSignals: cveIds.map((id) => `cve:${id}`),
334
+ limitations: cveIds.length === 0 ? ["No explicit CVE-YYYY-NNNN identifier found in text"] : [],
335
+ },
336
+ };
337
+ },
338
+ };
339
+ /** Default ordered registry of all built-in targets (specific targets first, generic web-spa last). */
340
+ export const BUILTIN_TARGETS = [
341
+ llmsTxtTargetPlugin,
342
+ githubTargetPlugin,
343
+ arxivTargetPlugin,
344
+ youtubeMediaTargetPlugin,
345
+ pdfOfficeTargetPlugin,
346
+ cveSecurityTargetPlugin,
347
+ shoppingTargetPlugin,
348
+ flightsTargetPlugin,
349
+ hotelsTargetPlugin,
350
+ realEstateTargetPlugin,
351
+ localBusinessTargetPlugin,
352
+ webSpaTargetPlugin,
353
+ ];
354
+ const customTargets = new Map();
355
+ /** Register a custom or community target plugin at runtime. */
356
+ export function registerTarget(plugin) {
357
+ customTargets.set(plugin.id, plugin);
358
+ }
359
+ /** List all registered target plugins (community overrides + built-ins). */
360
+ export function listTargets() {
361
+ const byId = new Map();
362
+ for (const t of customTargets.values()) {
363
+ byId.set(t.id, t);
364
+ }
365
+ for (const t of BUILTIN_TARGETS) {
366
+ if (!byId.has(t.id)) {
367
+ byId.set(t.id, t);
368
+ }
369
+ }
370
+ return Array.from(byId.values());
371
+ }
372
+ export function getTargetById(id) {
373
+ return customTargets.get(id) ?? BUILTIN_TARGETS.find((t) => t.id === id);
374
+ }
375
+ /** Resolve the highest-confidence target plugin for a URL, query, or category. */
376
+ export function resolveTarget(input) {
377
+ let bestPlugin = webSpaTargetPlugin;
378
+ let bestMatch = { matched: true, confidence: 0.1, reason: "Fallback web target" };
379
+ for (const plugin of listTargets()) {
380
+ const m = plugin.matches(input);
381
+ if (m.matched && m.confidence > bestMatch.confidence) {
382
+ bestPlugin = plugin;
383
+ bestMatch = m;
384
+ }
385
+ }
386
+ return { plugin: bestPlugin, match: bestMatch };
387
+ }
388
+ /** Run target-aware extraction on HTML/text and attach REA-style Evidence & Provenance. */
389
+ export async function inspectTarget(input) {
390
+ const start = Date.now();
391
+ const plugin = input.targetId
392
+ ? (getTargetById(input.targetId) ?? webSpaTargetPlugin)
393
+ : resolveTarget({
394
+ url: input.url,
395
+ query: input.query,
396
+ contentType: input.contentType,
397
+ category: input.category,
398
+ }).plugin;
399
+ if (plugin.extract) {
400
+ const res = await plugin.extract(input);
401
+ res.evidence.latencyMs = Date.now() - start;
402
+ return res;
403
+ }
404
+ const fallback = await webSpaTargetPlugin.extract(input);
405
+ fallback.targetId = plugin.id;
406
+ fallback.targetName = plugin.name;
407
+ fallback.category = plugin.category;
408
+ fallback.evidence.targetId = plugin.id;
409
+ fallback.evidence.latencyMs = Date.now() - start;
410
+ return fallback;
411
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pluggable Target & Extractor Registry types for Webcrawl Anything.
3
+ *
4
+ * Modeled on Reverse Engineer Anything (REA): every target declares what it can
5
+ * inspect, its local/cloud prerequisites, how to match URLs or search queries,
6
+ * and returns structured results paired with verifiable Evidence & Provenance.
7
+ */
8
+ export type TargetCategory = "web" | "documents" | "media" | "commerce" | "travel" | "real-estate" | "local" | "security" | "developer" | "academic";
9
+ export type TargetTier = "cache" | "static" | "impit-tls" | "impersonate" | "proxy-impersonate" | "cloudflare" | "stealth-browser" | "sandbox" | "native-parser" | "ocr" | "api";
10
+ export interface TargetEvidence {
11
+ /** Which target plugin handled the inspection */
12
+ targetId: string;
13
+ /** Transport or parser tier used to acquire the content */
14
+ tier: TargetTier;
15
+ /** SHA-256 of the raw content or canonical payload */
16
+ contentHash?: string;
17
+ /** End-to-end execution latency in milliseconds */
18
+ latencyMs?: number;
19
+ /** Canonical URL or upstream endpoint inspected */
20
+ sourceUrl?: string;
21
+ /** DOM selectors, JSON-LD blocks, API endpoints, or regex rules that matched */
22
+ matchedSignals: string[];
23
+ /** Explicit limitations, unverified fields, or skipped sections (REA-style honesty) */
24
+ limitations: string[];
25
+ }
26
+ export interface TargetMatch {
27
+ matched: boolean;
28
+ confidence: number;
29
+ reason?: string;
30
+ extractedParams?: Record<string, string>;
31
+ }
32
+ export interface TargetInspectInput {
33
+ url?: string;
34
+ query?: string;
35
+ html?: string;
36
+ markdown?: string;
37
+ bytes?: Uint8Array;
38
+ contentType?: string;
39
+ countryCode?: string;
40
+ }
41
+ export interface TargetInspectResult<T = unknown> {
42
+ targetId: string;
43
+ targetName: string;
44
+ category: TargetCategory;
45
+ title?: string;
46
+ markdown?: string;
47
+ structured?: T;
48
+ evidence: TargetEvidence;
49
+ }
50
+ export interface CrawlTargetPlugin<T = unknown> {
51
+ /** Unique slug (e.g. "web-spa", "pdf-office", "youtube", "shopping", "github", "arxiv", "cve") */
52
+ id: string;
53
+ /** Human-readable target name for `webcrawl targets` and README table */
54
+ name: string;
55
+ /** Category grouping */
56
+ category: TargetCategory;
57
+ /** One-line summary of what Webcrawl returns for this target (REA table column 2) */
58
+ returns: string;
59
+ /** Local/cloud requirements & guide note (REA table column 3) */
60
+ requirements: string;
61
+ /** Example URLs or queries for CLI / agent discovery */
62
+ examples: string[];
63
+ /** Fast synchronous check whether a URL or search query targets this plugin */
64
+ matches(input: {
65
+ url?: string;
66
+ query?: string;
67
+ contentType?: string;
68
+ category?: string;
69
+ }): TargetMatch;
70
+ /** Extract structured data + evidence from already-fetched HTML/text/bytes */
71
+ extract?(input: TargetInspectInput): Promise<TargetInspectResult<T>> | TargetInspectResult<T>;
72
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pluggable Target & Extractor Registry types for Webcrawl Anything.
3
+ *
4
+ * Modeled on Reverse Engineer Anything (REA): every target declares what it can
5
+ * inspect, its local/cloud prerequisites, how to match URLs or search queries,
6
+ * and returns structured results paired with verifiable Evidence & Provenance.
7
+ */
8
+ export {};
@@ -0,0 +1 @@
1
+ export * from "./targets/index.js";
@@ -0,0 +1 @@
1
+ export * from "./targets/index.js";
package/dist/format.js CHANGED
@@ -59,7 +59,11 @@ export function formatSearch(res) {
59
59
  })
60
60
  .join("\n")}`);
61
61
  }
62
- return parts.length ? parts.join("\n\n") : "No results.";
62
+ if (parts.length)
63
+ return parts.join("\n\n");
64
+ if (res.success === false && str(res.error))
65
+ return `Error: ${str(res.error)}`;
66
+ return "No results.";
63
67
  }
64
68
  export function formatCredits(res) {
65
69
  const c = res.credits;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createServer, createWebcrawlMcpServer, resolveSchemaProfile, type McpServerOptions, type SchemaProfile, type WebcrawlLikeClient, } from "./server.js";
2
+ export { loadLocalEngineClient, LOCAL_ENGINE_LOAD_ERROR } from "./load-local-engine.js";
2
3
  export { MAX_OUTPUT_CHARS, truncate } from "./format.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createServer, createWebcrawlMcpServer, resolveSchemaProfile, } from "./server.js";
2
+ export { loadLocalEngineClient, LOCAL_ENGINE_LOAD_ERROR } from "./load-local-engine.js";
2
3
  export { MAX_OUTPUT_CHARS, truncate } from "./format.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Load the zero-key local engine that ships inside this package (`dist/core`).
3
+ * Never falls back to webcrawl.app — callers must fail if this throws.
4
+ */
5
+ import type { WebcrawlLikeClient } from "./server.js";
6
+ export declare const LOCAL_ENGINE_LOAD_ERROR: string;
7
+ export declare function loadLocalEngineClient(): Promise<WebcrawlLikeClient>;
@@ -0,0 +1,22 @@
1
+ export const LOCAL_ENGINE_LOAD_ERROR = "webcrawl-mcp: failed to load the local engine bundled in this package (dist/core/local-engine.js). " +
2
+ "Reinstall @webcrawl/mcp. To use webcrawl.app cloud instead, set WEBCRAWL_API_KEY or run `webcrawl login`.";
3
+ async function importEngine(spec) {
4
+ const localMod = (await import(spec));
5
+ if (typeof localMod.createLocalEngineClient !== "function") {
6
+ throw new Error(`no createLocalEngineClient export from ${spec}`);
7
+ }
8
+ return localMod.createLocalEngineClient();
9
+ }
10
+ export async function loadLocalEngineClient() {
11
+ const errors = [];
12
+ const packaged = new URL("./core/local-engine.js", import.meta.url).href;
13
+ for (const spec of [packaged, "webcrawl/local-engine"]) {
14
+ try {
15
+ return await importEngine(spec);
16
+ }
17
+ catch (e) {
18
+ errors.push(`${spec}: ${e instanceof Error ? e.message : String(e)}`);
19
+ }
20
+ }
21
+ throw new Error(`${LOCAL_ENGINE_LOAD_ERROR}\n${errors.join("\n")}`);
22
+ }
package/dist/server.js CHANGED
@@ -66,6 +66,9 @@ export function createWebcrawlMcpServer(client, versionOrOpts = "0.1.0") {
66
66
  sites,
67
67
  scrapeContent: include_content,
68
68
  });
69
+ if (res.success === false && typeof res.error === "string") {
70
+ throw new Error(formatSearch(res) + formatCredits(res));
71
+ }
69
72
  return formatSearch(res) + formatCredits(res);
70
73
  }));
71
74
  server.registerTool("web_read", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcrawl/mcp",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "mcpName": "io.github.brainfish-ai/webcrawl",
5
5
  "description": "MCP server for Webcrawl Anything: search, scrape, map, crawl, parse and monitor the web from your agent. Runs locally with no API key, or on webcrawl.app cloud.",
6
6
  "type": "module",
@@ -47,13 +47,21 @@
47
47
  }
48
48
  },
49
49
  "scripts": {
50
- "build": "tsc -p tsconfig.build.json",
50
+ "build": "tsc -p tsconfig.build.json && tsc -p tsconfig.core.json",
51
51
  "prepack": "npm run build",
52
52
  "test": "node --conditions=development --import tsx --test test/*.test.ts"
53
53
  },
54
54
  "dependencies": {
55
+ "@ai-sdk/gateway": "^4.0.123",
56
+ "@firecrawl/pdf-inspector": "^1.25.2",
55
57
  "@modelcontextprotocol/sdk": "^1.32.2",
56
- "@webcrawl/client": "^1.4.2",
58
+ "@vercel/sandbox": "^1.0.0",
59
+ "@webcrawl/client": "^1.4.3",
60
+ "@webcrawl/curl-impersonate": "^1.4.3",
61
+ "ai": "^7.0.150",
62
+ "impit": "^0.14.7",
63
+ "mammoth": "^1.13.0",
64
+ "officeparser": "^8.2.0",
57
65
  "zod": "^4.0.0"
58
66
  },
59
67
  "engines": {