mcp-scraper 0.3.16 → 0.3.18
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/README.md +26 -58
- package/dist/bin/api-server.cjs +418 -53
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +4 -4
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +5 -5
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +12 -12
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +3 -3
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +2554 -781
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +1377 -9
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.js +2 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-7XARJHS6.js → chunk-2XCNU22L.js} +332 -13
- package/dist/chunk-2XCNU22L.js.map +1 -0
- package/dist/chunk-C7ZNIXOA.js +7 -0
- package/dist/chunk-C7ZNIXOA.js.map +1 -0
- package/dist/{chunk-ACIUCZ27.js → chunk-DE7WP4UU.js} +11 -11
- package/dist/chunk-DE7WP4UU.js.map +1 -0
- package/dist/{chunk-ZAUMSBV3.js → chunk-HDPWLYK7.js} +6 -6
- package/dist/{chunk-4OPKIDON.js → chunk-IGOEAKC7.js} +4 -6
- package/dist/{chunk-4OPKIDON.js.map → chunk-IGOEAKC7.js.map} +1 -1
- package/dist/{chunk-M2S27J6Z.js → chunk-ZAP7MYVO.js} +26 -1
- package/dist/chunk-ZAP7MYVO.js.map +1 -0
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-LPVBSE2E.js → server-3M3XCVOT.js} +328 -157
- package/dist/server-3M3XCVOT.js.map +1 -0
- package/dist/{worker-2WVKKCC7.js → worker-J6I5XP2F.js} +9 -10
- package/dist/{worker-2WVKKCC7.js.map → worker-J6I5XP2F.js.map} +1 -1
- package/docs/mcp-tool-manifest.generated.json +28 -27
- package/package.json +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +0 -1523
- package/dist/bin/browser-agent-stdio-server.cjs.map +0 -1
- package/dist/bin/browser-agent-stdio-server.d.cts +0 -1
- package/dist/bin/browser-agent-stdio-server.d.ts +0 -1
- package/dist/bin/browser-agent-stdio-server.js +0 -43
- package/dist/bin/browser-agent-stdio-server.js.map +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +0 -5601
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.d.cts +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.d.ts +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +0 -70
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +0 -1
- package/dist/chunk-3YGKXXUG.js +0 -133
- package/dist/chunk-3YGKXXUG.js.map +0 -1
- package/dist/chunk-7XARJHS6.js.map +0 -1
- package/dist/chunk-ACIUCZ27.js.map +0 -1
- package/dist/chunk-ISZWGIWL.js +0 -1332
- package/dist/chunk-ISZWGIWL.js.map +0 -1
- package/dist/chunk-M2S27J6Z.js.map +0 -1
- package/dist/chunk-SOMBZK3V.js +0 -7
- package/dist/chunk-SOMBZK3V.js.map +0 -1
- package/dist/chunk-WN7PBKMV.js +0 -28
- package/dist/chunk-WN7PBKMV.js.map +0 -1
- package/dist/server-LPVBSE2E.js.map +0 -1
- /package/dist/{chunk-ZAUMSBV3.js.map → chunk-HDPWLYK7.js.map} +0 -0
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
PACKAGE_VERSION
|
|
3
|
+
} from "./chunk-C7ZNIXOA.js";
|
|
4
4
|
import {
|
|
5
5
|
browserServiceProfileName,
|
|
6
|
-
browserServiceProfileSaveChanges
|
|
7
|
-
|
|
6
|
+
browserServiceProfileSaveChanges,
|
|
7
|
+
sanitizeVendorName
|
|
8
|
+
} from "./chunk-ZAP7MYVO.js";
|
|
8
9
|
import {
|
|
9
10
|
DEFAULT_MAPS_PROXY_MODE,
|
|
10
11
|
DEFAULT_PROXY_MODE
|
|
11
12
|
} from "./chunk-LFATOGDF.js";
|
|
12
|
-
import {
|
|
13
|
-
PACKAGE_VERSION
|
|
14
|
-
} from "./chunk-SOMBZK3V.js";
|
|
15
13
|
|
|
16
14
|
// src/harvest-timeout.ts
|
|
17
15
|
var VERCEL_FUNCTION_MAX_MS = 3e5;
|
|
@@ -27,6 +25,71 @@ function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
|
|
|
27
25
|
return { serverMs, clientMs };
|
|
28
26
|
}
|
|
29
27
|
|
|
28
|
+
// src/api/outbound-sanitize.ts
|
|
29
|
+
var KEY_RENAMES = {
|
|
30
|
+
kernel: "browserRuntime",
|
|
31
|
+
kernel_session_id: "browser_session_id",
|
|
32
|
+
kernel_delete_started: "session_cleanup_started",
|
|
33
|
+
kernel_delete_succeeded: "session_cleanup_succeeded",
|
|
34
|
+
kernel_delete_error: "session_cleanup_error",
|
|
35
|
+
kernelSessionId: "browserSessionId",
|
|
36
|
+
kernelDeleteStarted: "sessionCleanupStarted",
|
|
37
|
+
kernelDeleteSucceeded: "sessionCleanupSucceeded",
|
|
38
|
+
kernelDeleteError: "sessionCleanupError",
|
|
39
|
+
kernelProxyId: "proxyId"
|
|
40
|
+
};
|
|
41
|
+
var SANITIZED_VALUE_KEYS = /error|message/i;
|
|
42
|
+
var VENDOR_URL_KEYS = /* @__PURE__ */ new Set([
|
|
43
|
+
"hosted_url",
|
|
44
|
+
"hostedUrl",
|
|
45
|
+
"live_view_url",
|
|
46
|
+
"liveViewUrl",
|
|
47
|
+
"cdp_ws_url",
|
|
48
|
+
"cdpWsUrl",
|
|
49
|
+
"browser_live_view_url"
|
|
50
|
+
]);
|
|
51
|
+
var VENDOR_URL_RE = /\b(?:wss?|https?):\/\/[^\s"'<>]*\bonkernel\.com[^\s"'<>]*/gi;
|
|
52
|
+
function redactVendorUrls(value) {
|
|
53
|
+
return value.replace(VENDOR_URL_RE, "[browser-service]");
|
|
54
|
+
}
|
|
55
|
+
function sanitizeOutboundDiagnostics(value, parentKey = "") {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
let out = redactVendorUrls(value);
|
|
58
|
+
if (SANITIZED_VALUE_KEYS.test(parentKey) && /kernel/i.test(out)) {
|
|
59
|
+
out = sanitizeVendorName(out);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
|
|
64
|
+
if (value !== null && typeof value === "object") {
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [key, val] of Object.entries(value)) {
|
|
67
|
+
const renamed = KEY_RENAMES[key] ?? key;
|
|
68
|
+
if (VENDOR_URL_KEYS.has(key)) {
|
|
69
|
+
out[renamed] = null;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
out[renamed] = sanitizeOutboundDiagnostics(val, key);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function sanitizeAttempts(attempts) {
|
|
79
|
+
return attempts.map((a) => sanitizeOutboundDiagnostics(a));
|
|
80
|
+
}
|
|
81
|
+
function sanitizeHarvestResult(result) {
|
|
82
|
+
const diagnostics = result?.diagnostics;
|
|
83
|
+
if (!diagnostics?.debug) return result;
|
|
84
|
+
return {
|
|
85
|
+
...result,
|
|
86
|
+
diagnostics: {
|
|
87
|
+
...diagnostics,
|
|
88
|
+
debug: sanitizeOutboundDiagnostics(diagnostics.debug)
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
30
93
|
// src/mcp/server-instructions.ts
|
|
31
94
|
var SERVER_INSTRUCTIONS = `
|
|
32
95
|
This server scrapes and analyzes web, social, search, maps, and site data. Pick a tool by NAME using
|
|
@@ -43,7 +106,11 @@ ROUTING
|
|
|
43
106
|
- Workflows (multi-step): local directory build -> directory_workflow; rank blueprint -> rank_tracker_workflow;
|
|
44
107
|
AI-answer citation fan-out (AEO) -> query_fanout_workflow; deep research -> deep_research_workflow.
|
|
45
108
|
- Anything without a dedicated tool (e.g. Reddit, arbitrary logged-in sites) -> the browser_* agent
|
|
46
|
-
(browser_open, then navigate/read)
|
|
109
|
+
(browser_open, then navigate/read).
|
|
110
|
+
- Logged-in sites (ChatGPT, Claude, Reddit, any account): save the login first -> browser_profile_connect
|
|
111
|
+
(returns an mcpscraper.dev sign-in link for the user; one profile holds MANY logins, call it again with
|
|
112
|
+
the same profile + a new domain to add accounts) -> poll browser_profile_list until AUTHENTICATED ->
|
|
113
|
+
browser_open with that profile. browser_profile_list also shows what a profile is connected to.
|
|
47
114
|
|
|
48
115
|
NOTES
|
|
49
116
|
- Bulk / full-site crawls: call extract_site with rotateProxies:true for blocked or rate-limited sites.
|
|
@@ -256,6 +323,99 @@ function buildLinkGraph(pages, startUrl) {
|
|
|
256
323
|
return { edges, metrics };
|
|
257
324
|
}
|
|
258
325
|
|
|
326
|
+
// src/api/seo-link-report.ts
|
|
327
|
+
function registrableDomain(host) {
|
|
328
|
+
const h = host.replace(/^www\./, "").toLowerCase();
|
|
329
|
+
const parts = h.split(".");
|
|
330
|
+
return parts.length <= 2 ? h : parts.slice(-2).join(".");
|
|
331
|
+
}
|
|
332
|
+
function buildLinkReport(edges, metrics, siteUrl) {
|
|
333
|
+
let siteReg = "";
|
|
334
|
+
try {
|
|
335
|
+
siteReg = registrableDomain(new URL(siteUrl).hostname);
|
|
336
|
+
} catch {
|
|
337
|
+
siteReg = "";
|
|
338
|
+
}
|
|
339
|
+
const internalEdges = edges.filter((e) => e.internal);
|
|
340
|
+
const pages = metrics.length;
|
|
341
|
+
const orphans = metrics.filter((m) => m.orphan).length;
|
|
342
|
+
const brokenInternal = internalEdges.filter((e) => e.targetStatus != null && e.targetStatus >= 400).length;
|
|
343
|
+
const sumInlinks = metrics.reduce((a, m) => a + m.inlinks, 0);
|
|
344
|
+
const sumOutlinks = metrics.reduce((a, m) => a + m.outlinksInternal + m.outlinksExternal, 0);
|
|
345
|
+
const distribution = { zero: 0, oneToTwo: 0, threeToTen: 0, elevenPlus: 0 };
|
|
346
|
+
for (const m of metrics) {
|
|
347
|
+
if (m.inlinks === 0) distribution.zero++;
|
|
348
|
+
else if (m.inlinks <= 2) distribution.oneToTwo++;
|
|
349
|
+
else if (m.inlinks <= 10) distribution.threeToTen++;
|
|
350
|
+
else distribution.elevenPlus++;
|
|
351
|
+
}
|
|
352
|
+
const topByInlinks = [...metrics].sort((a, b) => b.inlinks - a.inlinks).slice(0, 20).map((m) => ({ url: m.url, inlinks: m.inlinks, outlinksInternal: m.outlinksInternal, outlinksExternal: m.outlinksExternal }));
|
|
353
|
+
const domMap = /* @__PURE__ */ new Map();
|
|
354
|
+
let externalTotal = 0;
|
|
355
|
+
for (const e of edges) {
|
|
356
|
+
let domain;
|
|
357
|
+
try {
|
|
358
|
+
domain = registrableDomain(new URL(e.to).hostname);
|
|
359
|
+
} catch {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (!domain || domain === siteReg) continue;
|
|
363
|
+
externalTotal++;
|
|
364
|
+
const d = domMap.get(domain) ?? { links: 0, nofollow: 0, pages: /* @__PURE__ */ new Set() };
|
|
365
|
+
d.links++;
|
|
366
|
+
if (e.nofollow) d.nofollow++;
|
|
367
|
+
d.pages.add(e.from);
|
|
368
|
+
domMap.set(domain, d);
|
|
369
|
+
}
|
|
370
|
+
const externalDomains = [...domMap.entries()].map(([domain, d]) => ({ domain, links: d.links, nofollow: d.nofollow, pages: d.pages.size })).sort((a, b) => b.links - a.links);
|
|
371
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
372
|
+
return {
|
|
373
|
+
summary: {
|
|
374
|
+
internal: {
|
|
375
|
+
totalLinks: internalEdges.length,
|
|
376
|
+
pages,
|
|
377
|
+
orphans,
|
|
378
|
+
brokenInternal,
|
|
379
|
+
avgInlinks: pages ? round(sumInlinks / pages) : 0,
|
|
380
|
+
avgOutlinks: pages ? round(sumOutlinks / pages) : 0,
|
|
381
|
+
distribution,
|
|
382
|
+
topByInlinks
|
|
383
|
+
},
|
|
384
|
+
external: {
|
|
385
|
+
totalLinks: externalTotal,
|
|
386
|
+
uniqueDomains: externalDomains.length,
|
|
387
|
+
topDomains: externalDomains.slice(0, 20)
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
externalDomains
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function renderLinkReport(r) {
|
|
394
|
+
const s = r.summary;
|
|
395
|
+
const lines = [
|
|
396
|
+
`## Link analysis`,
|
|
397
|
+
`**Internal:** ${s.internal.totalLinks} links \xB7 ${s.internal.pages} pages \xB7 avg ${s.internal.avgInlinks} inlinks/page \xB7 ${s.internal.orphans} orphans \xB7 ${s.internal.brokenInternal} broken`,
|
|
398
|
+
`**External:** ${s.external.totalLinks} links to ${s.external.uniqueDomains} domains`,
|
|
399
|
+
``,
|
|
400
|
+
`### Inlink distribution`,
|
|
401
|
+
`- 0 inlinks (orphans): ${s.internal.distribution.zero}`,
|
|
402
|
+
`- 1\u20132: ${s.internal.distribution.oneToTwo}`,
|
|
403
|
+
`- 3\u201310: ${s.internal.distribution.threeToTen}`,
|
|
404
|
+
`- 11+: ${s.internal.distribution.elevenPlus}`,
|
|
405
|
+
``,
|
|
406
|
+
`### Top 20 internal pages by inlinks`,
|
|
407
|
+
`| inlinks | out (int/ext) | URL |`,
|
|
408
|
+
`|---|---|---|`,
|
|
409
|
+
...s.internal.topByInlinks.map((p) => `| ${p.inlinks} | ${p.outlinksInternal}/${p.outlinksExternal} | ${p.url} |`),
|
|
410
|
+
``,
|
|
411
|
+
`### Top 20 external domains by links`,
|
|
412
|
+
`| links | nofollow | from pages | domain |`,
|
|
413
|
+
`|---|---|---|---|`,
|
|
414
|
+
...s.external.topDomains.map((d) => `| ${d.links} | ${d.nofollow} | ${d.pages} | ${d.domain} |`)
|
|
415
|
+
];
|
|
416
|
+
return lines.join("\n");
|
|
417
|
+
}
|
|
418
|
+
|
|
259
419
|
// src/api/seo-issues.ts
|
|
260
420
|
var THIN_WORDS = 200;
|
|
261
421
|
var TITLE_MAX = 60;
|
|
@@ -564,6 +724,9 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
564
724
|
`- \`pages.jsonl\` \u2014 per-page SEO fields, one JSON object per page`,
|
|
565
725
|
`- \`links.jsonl\` \u2014 internal/external link edges (anchor, rel, target status)`,
|
|
566
726
|
`- \`link-metrics.jsonl\` \u2014 inlinks, crawl depth, orphan flags per page`,
|
|
727
|
+
`- \`link-report.md\` \u2014 link analysis: top internal pages by inlinks, top external domains, distribution`,
|
|
728
|
+
`- \`links-summary.json\` \u2014 link totals + top-20 internal pages + top-20 external domains`,
|
|
729
|
+
`- \`external-domains.json\` \u2014 every external domain linked to, ordered by link count`,
|
|
567
730
|
...imageAudit ? [
|
|
568
731
|
`- \`images.jsonl\` \u2014 every image with byte size, format, over-100KB and legacy-format flags`,
|
|
569
732
|
`- \`images-summary.json\` \u2014 image totals (count, total weight, over-100KB, legacy)`
|
|
@@ -599,7 +762,13 @@ ${indexRows.join("\n")}`
|
|
|
599
762
|
writeFileSync(reportFile, seo.reportMd + (imageAudit ? `
|
|
600
763
|
|
|
601
764
|
${renderImageSection(imageAudit)}` : ""), "utf8");
|
|
602
|
-
|
|
765
|
+
const linkReportFile = join(dir, "link-report.md");
|
|
766
|
+
const linksSummaryFile = join(dir, "links-summary.json");
|
|
767
|
+
const externalDomainsFile = join(dir, "external-domains.json");
|
|
768
|
+
writeFileSync(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
|
|
769
|
+
writeFileSync(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
|
|
770
|
+
writeFileSync(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
|
|
771
|
+
seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
|
|
603
772
|
if (imageAudit) {
|
|
604
773
|
const imagesJsonl = join(dir, "images.jsonl");
|
|
605
774
|
const imagesSummary = join(dir, "images-summary.json");
|
|
@@ -1051,6 +1220,7 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
1051
1220
|
metrics: [...metrics.values()],
|
|
1052
1221
|
issues,
|
|
1053
1222
|
reportMd: renderIssueReport(siteUrl, pages, issues, metrics),
|
|
1223
|
+
linkReport: buildLinkReport(edges, [...metrics.values()], siteUrl),
|
|
1054
1224
|
branding: branding ?? void 0
|
|
1055
1225
|
};
|
|
1056
1226
|
}
|
|
@@ -1159,6 +1329,9 @@ async function formatAuditSite(raw, input) {
|
|
|
1159
1329
|
const topIssues = Object.entries(seo.issues).sort((a, b) => b[1].count - a[1].count).slice(0, 8).map(([k, v]) => `- \`${k}\` \u2014 ${v.count}`).join("\n");
|
|
1160
1330
|
const img = imageAudit.summary;
|
|
1161
1331
|
const imgLine = `${img.unique} images \xB7 ${img.totalSize} total \xB7 ${img.over100kb} over 100 KB \xB7 ${img.legacyFormat} legacy format`;
|
|
1332
|
+
const lr = seo.linkReport.summary;
|
|
1333
|
+
const topExt = lr.external.topDomains.slice(0, 3).map((dm) => `${dm.domain} (${dm.links})`).join(", ") || "\u2014";
|
|
1334
|
+
const linkLine = `${lr.internal.totalLinks} internal / ${lr.external.totalLinks} external links \xB7 ${lr.internal.orphans} orphans \xB7 ${lr.internal.brokenInternal} broken \xB7 avg ${lr.internal.avgInlinks} inlinks/page \xB7 top external: ${topExt}`;
|
|
1162
1335
|
const durationLine = `**${pages.length} pages** \xB7 ${((d.durationMs ?? 0) / 1e3).toFixed(1)}s`;
|
|
1163
1336
|
const structuredContent = {
|
|
1164
1337
|
url: input.url,
|
|
@@ -1166,7 +1339,8 @@ async function formatAuditSite(raw, input) {
|
|
|
1166
1339
|
durationMs: d.durationMs ?? 0,
|
|
1167
1340
|
bulkFolder: bulk?.dir ?? null,
|
|
1168
1341
|
issues: Object.fromEntries(Object.entries(seo.issues).map(([k, v]) => [k, v.count])),
|
|
1169
|
-
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat }
|
|
1342
|
+
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
|
|
1343
|
+
links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
|
|
1170
1344
|
};
|
|
1171
1345
|
const location = bulk ? `
|
|
1172
1346
|
## \u{1F4C1} Technical audit saved
|
|
@@ -1185,6 +1359,9 @@ Report saving is disabled in this environment. Enable \`MCP_SCRAPER_SAVE_REPORTS
|
|
|
1185
1359
|
## Top issues
|
|
1186
1360
|
${topIssues || "_none found_"}`,
|
|
1187
1361
|
`
|
|
1362
|
+
## Links
|
|
1363
|
+
${linkLine}`,
|
|
1364
|
+
`
|
|
1188
1365
|
## Images
|
|
1189
1366
|
${imgLine}`
|
|
1190
1367
|
].join("\n");
|
|
@@ -2715,6 +2892,13 @@ var AuditSiteOutputSchema = {
|
|
|
2715
2892
|
totalBytes: z.number().min(0),
|
|
2716
2893
|
over100kb: z.number().int().min(0),
|
|
2717
2894
|
legacyFormat: z.number().int().min(0)
|
|
2895
|
+
}),
|
|
2896
|
+
links: z.object({
|
|
2897
|
+
internal: z.number().int().min(0),
|
|
2898
|
+
external: z.number().int().min(0),
|
|
2899
|
+
orphans: z.number().int().min(0),
|
|
2900
|
+
brokenInternal: z.number().int().min(0),
|
|
2901
|
+
externalDomains: z.number().int().min(0)
|
|
2718
2902
|
})
|
|
2719
2903
|
};
|
|
2720
2904
|
var MapsPlaceIntelOutputSchema = {
|
|
@@ -3566,7 +3750,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
3566
3750
|
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
3567
3751
|
server.registerTool("audit_site", {
|
|
3568
3752
|
title: "Technical SEO Audit",
|
|
3569
|
-
description: withReportNote("Run a full technical SEO audit (Screaming-Frog-style) on a public website. Crawls up to maxPages and produces a complete on-page + crawl analysis: per-page title/meta lengths, heading breakdown, indexability and canonical status, schema, an internal link graph
|
|
3753
|
+
description: withReportNote("Run a full technical SEO audit (Screaming-Frog-style) on a public website. Crawls up to maxPages and produces a complete on-page + crawl analysis: per-page title/meta lengths, heading breakdown, indexability and canonical status, schema, an internal link graph (inlinks/orphans/crawl-depth), a link analysis (top internal pages by inlinks, top external domains by link count, broken/orphan counts), an issues report, and an image audit (byte size, format, over-100KB and legacy-format flags). Writes everything to a local folder (report.md, issues.json, pages.jsonl, links.jsonl, link-metrics.jsonl, link-report.md, links-summary.json, external-domains.json, images.jsonl) plus per-page content, and returns a headline summary plus the folder path. Use this when the user wants an audit or analysis; use extract_site when they just want page content."),
|
|
3570
3754
|
inputSchema: AuditSiteInputSchema,
|
|
3571
3755
|
outputSchema: AuditSiteOutputSchema,
|
|
3572
3756
|
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
@@ -3975,9 +4159,143 @@ var HttpMcpToolExecutor = class {
|
|
|
3975
4159
|
}
|
|
3976
4160
|
};
|
|
3977
4161
|
|
|
4162
|
+
// src/services/fanout/export.ts
|
|
4163
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4164
|
+
import { homedir as homedir2 } from "os";
|
|
4165
|
+
import { join as join3 } from "path";
|
|
4166
|
+
import Papa from "papaparse";
|
|
4167
|
+
function outputBaseDir2() {
|
|
4168
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join3(homedir2(), "Downloads", "mcp-scraper");
|
|
4169
|
+
}
|
|
4170
|
+
function safe(value) {
|
|
4171
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "capture";
|
|
4172
|
+
}
|
|
4173
|
+
function writeTable(path, rows, delimiter) {
|
|
4174
|
+
writeFileSync2(path, Papa.unparse(rows.length ? rows : [{}], { delimiter }));
|
|
4175
|
+
}
|
|
4176
|
+
function exportFanout(enriched) {
|
|
4177
|
+
const stamp = safe(enriched.capturedAt.replace(/[:.]/g, "-"));
|
|
4178
|
+
const outputDir = outputBaseDir2();
|
|
4179
|
+
const relativeDir = join3("fanout", `${stamp}-${safe(enriched.platform)}`);
|
|
4180
|
+
const dir = join3(outputDir, relativeDir);
|
|
4181
|
+
mkdirSync2(dir, { recursive: true });
|
|
4182
|
+
const queryRows = enriched.queries.map((q, i) => ({ index: i + 1, query: q }));
|
|
4183
|
+
const citationRows = enriched.aggregates.citationOrder.map((c) => {
|
|
4184
|
+
const src = enriched.citedUrls.find((s) => s.url === c.url);
|
|
4185
|
+
return { rank: c.rank, domain: c.domain, url: c.url, timesCited: c.timesCited, siteType: src?.siteType ?? "", title: src?.title ?? "" };
|
|
4186
|
+
});
|
|
4187
|
+
const sourceRows = enriched.browsedUrls.map((s) => ({ domain: s.domain, url: s.url, cited: s.cited, timesCited: s.timesCited, siteType: s.siteType, title: s.title, round: s.round ?? "" }));
|
|
4188
|
+
const browsedOnlyRows = enriched.browsedOnly.map((s) => ({ domain: s.domain, url: s.url, cited: s.cited, timesCited: s.timesCited, siteType: s.siteType, title: s.title, round: s.round ?? "" }));
|
|
4189
|
+
const snippetRows = enriched.snippets.map((s) => ({ domain: s.domain, url: s.url, title: s.title, text: s.text }));
|
|
4190
|
+
const domainRows = enriched.aggregates.topSites.map((d) => ({ domain: d.domain, count: d.count, cited: d.cited, timesCited: d.timesCited, siteType: d.siteType }));
|
|
4191
|
+
const relativePaths = {
|
|
4192
|
+
relativeTo: "MCP_SCRAPER_OUTPUT_DIR or ~/Downloads/mcp-scraper",
|
|
4193
|
+
dir: relativeDir,
|
|
4194
|
+
json: join3(relativeDir, "fanout.json"),
|
|
4195
|
+
queriesCsv: join3(relativeDir, "queries.csv"),
|
|
4196
|
+
queriesTsv: join3(relativeDir, "queries.tsv"),
|
|
4197
|
+
citationsCsv: join3(relativeDir, "citations.csv"),
|
|
4198
|
+
sourcesCsv: join3(relativeDir, "sources.csv"),
|
|
4199
|
+
browsedOnlyCsv: join3(relativeDir, "browsed-only.csv"),
|
|
4200
|
+
snippetsCsv: join3(relativeDir, "snippets.csv"),
|
|
4201
|
+
domainsCsv: join3(relativeDir, "domains.csv"),
|
|
4202
|
+
report: join3(relativeDir, "report.html")
|
|
4203
|
+
};
|
|
4204
|
+
writeFileSync2(join3(outputDir, relativePaths.json), JSON.stringify(enriched, null, 2));
|
|
4205
|
+
writeTable(join3(outputDir, relativePaths.queriesCsv), queryRows, ",");
|
|
4206
|
+
writeTable(join3(outputDir, relativePaths.queriesTsv), queryRows, " ");
|
|
4207
|
+
writeTable(join3(outputDir, relativePaths.citationsCsv), citationRows, ",");
|
|
4208
|
+
writeTable(join3(outputDir, relativePaths.sourcesCsv), sourceRows, ",");
|
|
4209
|
+
writeTable(join3(outputDir, relativePaths.browsedOnlyCsv), browsedOnlyRows, ",");
|
|
4210
|
+
writeTable(join3(outputDir, relativePaths.snippetsCsv), snippetRows, ",");
|
|
4211
|
+
writeTable(join3(outputDir, relativePaths.domainsCsv), domainRows, ",");
|
|
4212
|
+
writeFileSync2(join3(outputDir, relativePaths.report), renderReportHtml(enriched));
|
|
4213
|
+
return relativePaths;
|
|
4214
|
+
}
|
|
4215
|
+
function esc(s) {
|
|
4216
|
+
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4217
|
+
}
|
|
4218
|
+
function renderReportHtml(enriched) {
|
|
4219
|
+
const data = JSON.stringify(enriched).replace(/</g, "\\u003c");
|
|
4220
|
+
return `<!doctype html>
|
|
4221
|
+
<html lang="en">
|
|
4222
|
+
<head>
|
|
4223
|
+
<meta charset="utf-8" />
|
|
4224
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
4225
|
+
<title>AI Search Fan-Out \u2014 ${esc(enriched.platform)}</title>
|
|
4226
|
+
<style>
|
|
4227
|
+
:root{--bg:#0b0d12;--panel:#151922;--line:#252b38;--ink:#e8ecf3;--mut:#8b95a7;--acc:#5b9dff;--cite:#39d98a;--browse:#5a6677}
|
|
4228
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}
|
|
4229
|
+
.wrap{max-width:980px;margin:0 auto;padding:28px 20px 80px}
|
|
4230
|
+
h1{font-size:20px;margin:0 0 2px}.sub{color:var(--mut);margin:0 0 20px;font-size:13px}
|
|
4231
|
+
.stat{font-size:30px;font-weight:700}.stat small{font-size:13px;font-weight:400;color:var(--mut)}
|
|
4232
|
+
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:18px 0}
|
|
4233
|
+
.card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
|
|
4234
|
+
.card b{font-size:22px;display:block}.card span{color:var(--mut);font-size:12px}
|
|
4235
|
+
.bars{margin:8px 0 22px}.bar{display:flex;align-items:center;gap:8px;margin:4px 0}
|
|
4236
|
+
.bar .lab{width:150px;color:var(--mut);font-size:12px;text-align:right}.bar .track{flex:1;background:#10141c;border-radius:5px;overflow:hidden}
|
|
4237
|
+
.bar .fill{background:var(--acc);height:16px}.bar .n{width:34px;font-size:12px;color:var(--mut)}
|
|
4238
|
+
.tabs{display:flex;gap:8px;margin:22px 0 12px}.tab{background:var(--panel);border:1px solid var(--line);color:var(--ink);padding:7px 14px;border-radius:8px;cursor:pointer}
|
|
4239
|
+
.tab.on{background:var(--acc);border-color:var(--acc);color:#04101f;font-weight:600}
|
|
4240
|
+
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}
|
|
4241
|
+
.chip{background:var(--panel);border:1px solid var(--line);color:var(--mut);padding:4px 10px;border-radius:20px;cursor:pointer;font-size:12px}
|
|
4242
|
+
.chip.on{border-color:var(--acc);color:var(--ink)}.chip b{color:var(--ink)}
|
|
4243
|
+
.row{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:6px 0;display:flex;gap:10px;align-items:flex-start}
|
|
4244
|
+
.row .idx{color:var(--mut);min-width:22px}.row .main{flex:1}
|
|
4245
|
+
.tag{font-size:11px;padding:2px 7px;border-radius:6px;border:1px solid var(--line);color:var(--mut);margin-right:5px}
|
|
4246
|
+
.dom{font-weight:600}.url{color:var(--mut);font-size:12px;word-break:break-all}
|
|
4247
|
+
.pill{font-size:11px;padding:2px 7px;border-radius:6px}.pill.cite{background:rgba(57,217,138,.15);color:var(--cite)}.pill.brow{background:rgba(90,102,119,.2);color:#aeb8c8}
|
|
4248
|
+
.copy{background:none;border:1px solid var(--line);color:var(--mut);border-radius:6px;cursor:pointer;font-size:11px;padding:2px 8px}
|
|
4249
|
+
.hide{display:none}
|
|
4250
|
+
</style>
|
|
4251
|
+
</head>
|
|
4252
|
+
<body><div class="wrap">
|
|
4253
|
+
<h1>AI Search Fan-Out</h1>
|
|
4254
|
+
<p class="sub" id="sub"></p>
|
|
4255
|
+
<div class="stat" id="stat"></div>
|
|
4256
|
+
<div class="cards" id="cards"></div>
|
|
4257
|
+
<div id="charts"></div>
|
|
4258
|
+
<div class="tabs"><button class="tab on" data-tab="q">Queries</button><button class="tab" data-tab="u">URLs</button></div>
|
|
4259
|
+
<div class="chips" id="chips"></div>
|
|
4260
|
+
<div id="list"></div>
|
|
4261
|
+
</div>
|
|
4262
|
+
<script>
|
|
4263
|
+
const D=${data};
|
|
4264
|
+
const el=(t,c,h)=>{const e=document.createElement(t);if(c)e.className=c;if(h!=null)e.innerHTML=h;return e};
|
|
4265
|
+
document.getElementById('sub').textContent=D.platform+' \xB7 '+(D.meta.model||'model n/a')+' \xB7 '+D.meta.rounds+' search round(s) \xB7 prompt: '+(D.prompt||'');
|
|
4266
|
+
document.getElementById('stat').innerHTML='Total Fan-Out: '+D.counts.subQueries+' <small>sub-queries \xB7 '+D.counts.browsed+' URLs researched \xB7 '+D.counts.cited+' cited</small>';
|
|
4267
|
+
const cards=[['Sub-queries',D.counts.subQueries],['Researched',D.counts.browsed],['Cited',D.counts.cited],['Browsed-only',D.counts.browsedOnly]];
|
|
4268
|
+
const cc=document.getElementById('cards');cards.forEach(([k,v])=>{const c=el('div','card');c.appendChild(el('b',null,v));c.appendChild(el('span',null,k));cc.appendChild(c)});
|
|
4269
|
+
function barChart(title,obj){const max=Math.max(1,...Object.values(obj));const box=el('div','bars');box.appendChild(el('div',null,'<b>'+title+'</b>'));Object.entries(obj).forEach(([k,v])=>{const r=el('div','bar');r.appendChild(el('div','lab',k));const t=el('div','track');const f=el('div','fill');f.style.width=(v/max*100)+'%';t.appendChild(f);r.appendChild(t);r.appendChild(el('div','n',v));box.appendChild(r)});return box}
|
|
4270
|
+
const charts=document.getElementById('charts');
|
|
4271
|
+
charts.appendChild(barChart('URL Categories',D.aggregates.byCategory));
|
|
4272
|
+
let tab='q',filter=null;
|
|
4273
|
+
const chips=document.getElementById('chips'),list=document.getElementById('list');
|
|
4274
|
+
function render(){
|
|
4275
|
+
chips.innerHTML='';list.innerHTML='';
|
|
4276
|
+
if(tab==='q'){
|
|
4277
|
+
D.queries.forEach((q,i)=>{const r=el('div','row');r.appendChild(el('div','idx',i+1));const m=el('div','main');m.appendChild(el('div',null,esc(q)));r.appendChild(m);const b=el('button','copy','copy');b.onclick=()=>navigator.clipboard.writeText(q);r.appendChild(b);list.appendChild(r)});
|
|
4278
|
+
} else {
|
|
4279
|
+
const counts={};D.browsedUrls.forEach(s=>{counts[s.siteType]=(counts[s.siteType]||0)+1});
|
|
4280
|
+
Object.entries(counts).forEach(([k,v])=>{const c=el('button','chip'+(filter===k?' on':''),k+' <b>'+v+'</b>');c.onclick=()=>{filter=filter===k?null:k;render()};chips.appendChild(c)});
|
|
4281
|
+
D.browsedUrls.forEach(s=>{if(filter&&s.siteType!==filter)return;const r=el('div','row');const m=el('div','main');m.appendChild(el('div',null,'<span class="dom">'+esc(s.domain)+'</span> <span class="pill '+(s.cited?'cite':'brow')+'">'+(s.cited?'cited '+s.timesCited+'\xD7':'browsed')+'</span> <span class="tag">'+s.siteType+'</span>'));m.appendChild(el('div','url',esc(s.url)));r.appendChild(m);list.appendChild(r)});
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
4285
|
+
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));t.classList.add('on');tab=t.dataset.tab;filter=null;render()});
|
|
4286
|
+
render();
|
|
4287
|
+
</script>
|
|
4288
|
+
</body></html>`;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
3978
4291
|
export {
|
|
3979
4292
|
harvestTimeoutBudget,
|
|
4293
|
+
sanitizeOutboundDiagnostics,
|
|
4294
|
+
sanitizeAttempts,
|
|
4295
|
+
sanitizeHarvestResult,
|
|
3980
4296
|
buildLinkGraph,
|
|
4297
|
+
buildLinkReport,
|
|
4298
|
+
renderLinkReport,
|
|
3981
4299
|
computeIssues,
|
|
3982
4300
|
renderIssueReport,
|
|
3983
4301
|
auditImages,
|
|
@@ -3994,6 +4312,7 @@ export {
|
|
|
3994
4312
|
liveWebToolAnnotations,
|
|
3995
4313
|
buildPaaExtractorMcpServer,
|
|
3996
4314
|
registerPaaExtractorMcpTools,
|
|
3997
|
-
HttpMcpToolExecutor
|
|
4315
|
+
HttpMcpToolExecutor,
|
|
4316
|
+
exportFanout
|
|
3998
4317
|
};
|
|
3999
|
-
//# sourceMappingURL=chunk-
|
|
4318
|
+
//# sourceMappingURL=chunk-2XCNU22L.js.map
|