mcp-scraper 0.3.15 → 0.3.17
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/dist/bin/api-server.cjs +420 -25
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +371 -20
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +4 -4
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +369 -18
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/{chunk-7Y6JDBML.js → chunk-ACIUCZ27.js} +3 -3
- package/dist/chunk-ACIUCZ27.js.map +1 -0
- package/dist/{chunk-WQAIOY2D.js → chunk-N33YEY6W.js} +374 -19
- package/dist/chunk-N33YEY6W.js.map +1 -0
- package/dist/chunk-PVWWY5NV.js +7 -0
- package/dist/chunk-PVWWY5NV.js.map +1 -0
- package/dist/{chunk-WJ2LWHPA.js → chunk-UFD7N36R.js} +2 -2
- package/dist/{server-UOQ2JIUQ.js → server-TKWY47JA.js} +43 -10
- package/dist/server-TKWY47JA.js.map +1 -0
- package/docs/mcp-tool-manifest.generated.json +45 -10
- package/package.json +1 -1
- package/dist/chunk-2MX5WOII.js +0 -7
- package/dist/chunk-2MX5WOII.js.map +0 -1
- package/dist/chunk-7Y6JDBML.js.map +0 -1
- package/dist/chunk-WQAIOY2D.js.map +0 -1
- package/dist/server-UOQ2JIUQ.js.map +0 -1
- /package/dist/{chunk-WJ2LWHPA.js.map → chunk-UFD7N36R.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -9054,6 +9054,104 @@ var init_seo_link_graph = __esm({
|
|
|
9054
9054
|
}
|
|
9055
9055
|
});
|
|
9056
9056
|
|
|
9057
|
+
// src/api/seo-link-report.ts
|
|
9058
|
+
function registrableDomain(host) {
|
|
9059
|
+
const h = host.replace(/^www\./, "").toLowerCase();
|
|
9060
|
+
const parts = h.split(".");
|
|
9061
|
+
return parts.length <= 2 ? h : parts.slice(-2).join(".");
|
|
9062
|
+
}
|
|
9063
|
+
function buildLinkReport(edges, metrics, siteUrl) {
|
|
9064
|
+
let siteReg = "";
|
|
9065
|
+
try {
|
|
9066
|
+
siteReg = registrableDomain(new URL(siteUrl).hostname);
|
|
9067
|
+
} catch {
|
|
9068
|
+
siteReg = "";
|
|
9069
|
+
}
|
|
9070
|
+
const internalEdges = edges.filter((e) => e.internal);
|
|
9071
|
+
const pages = metrics.length;
|
|
9072
|
+
const orphans = metrics.filter((m) => m.orphan).length;
|
|
9073
|
+
const brokenInternal = internalEdges.filter((e) => e.targetStatus != null && e.targetStatus >= 400).length;
|
|
9074
|
+
const sumInlinks = metrics.reduce((a, m) => a + m.inlinks, 0);
|
|
9075
|
+
const sumOutlinks = metrics.reduce((a, m) => a + m.outlinksInternal + m.outlinksExternal, 0);
|
|
9076
|
+
const distribution = { zero: 0, oneToTwo: 0, threeToTen: 0, elevenPlus: 0 };
|
|
9077
|
+
for (const m of metrics) {
|
|
9078
|
+
if (m.inlinks === 0) distribution.zero++;
|
|
9079
|
+
else if (m.inlinks <= 2) distribution.oneToTwo++;
|
|
9080
|
+
else if (m.inlinks <= 10) distribution.threeToTen++;
|
|
9081
|
+
else distribution.elevenPlus++;
|
|
9082
|
+
}
|
|
9083
|
+
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 }));
|
|
9084
|
+
const domMap = /* @__PURE__ */ new Map();
|
|
9085
|
+
let externalTotal = 0;
|
|
9086
|
+
for (const e of edges) {
|
|
9087
|
+
let domain;
|
|
9088
|
+
try {
|
|
9089
|
+
domain = registrableDomain(new URL(e.to).hostname);
|
|
9090
|
+
} catch {
|
|
9091
|
+
continue;
|
|
9092
|
+
}
|
|
9093
|
+
if (!domain || domain === siteReg) continue;
|
|
9094
|
+
externalTotal++;
|
|
9095
|
+
const d = domMap.get(domain) ?? { links: 0, nofollow: 0, pages: /* @__PURE__ */ new Set() };
|
|
9096
|
+
d.links++;
|
|
9097
|
+
if (e.nofollow) d.nofollow++;
|
|
9098
|
+
d.pages.add(e.from);
|
|
9099
|
+
domMap.set(domain, d);
|
|
9100
|
+
}
|
|
9101
|
+
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);
|
|
9102
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
9103
|
+
return {
|
|
9104
|
+
summary: {
|
|
9105
|
+
internal: {
|
|
9106
|
+
totalLinks: internalEdges.length,
|
|
9107
|
+
pages,
|
|
9108
|
+
orphans,
|
|
9109
|
+
brokenInternal,
|
|
9110
|
+
avgInlinks: pages ? round(sumInlinks / pages) : 0,
|
|
9111
|
+
avgOutlinks: pages ? round(sumOutlinks / pages) : 0,
|
|
9112
|
+
distribution,
|
|
9113
|
+
topByInlinks
|
|
9114
|
+
},
|
|
9115
|
+
external: {
|
|
9116
|
+
totalLinks: externalTotal,
|
|
9117
|
+
uniqueDomains: externalDomains.length,
|
|
9118
|
+
topDomains: externalDomains.slice(0, 20)
|
|
9119
|
+
}
|
|
9120
|
+
},
|
|
9121
|
+
externalDomains
|
|
9122
|
+
};
|
|
9123
|
+
}
|
|
9124
|
+
function renderLinkReport(r) {
|
|
9125
|
+
const s = r.summary;
|
|
9126
|
+
const lines = [
|
|
9127
|
+
`## Link analysis`,
|
|
9128
|
+
`**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`,
|
|
9129
|
+
`**External:** ${s.external.totalLinks} links to ${s.external.uniqueDomains} domains`,
|
|
9130
|
+
``,
|
|
9131
|
+
`### Inlink distribution`,
|
|
9132
|
+
`- 0 inlinks (orphans): ${s.internal.distribution.zero}`,
|
|
9133
|
+
`- 1\u20132: ${s.internal.distribution.oneToTwo}`,
|
|
9134
|
+
`- 3\u201310: ${s.internal.distribution.threeToTen}`,
|
|
9135
|
+
`- 11+: ${s.internal.distribution.elevenPlus}`,
|
|
9136
|
+
``,
|
|
9137
|
+
`### Top 20 internal pages by inlinks`,
|
|
9138
|
+
`| inlinks | out (int/ext) | URL |`,
|
|
9139
|
+
`|---|---|---|`,
|
|
9140
|
+
...s.internal.topByInlinks.map((p) => `| ${p.inlinks} | ${p.outlinksInternal}/${p.outlinksExternal} | ${p.url} |`),
|
|
9141
|
+
``,
|
|
9142
|
+
`### Top 20 external domains by links`,
|
|
9143
|
+
`| links | nofollow | from pages | domain |`,
|
|
9144
|
+
`|---|---|---|---|`,
|
|
9145
|
+
...s.external.topDomains.map((d) => `| ${d.links} | ${d.nofollow} | ${d.pages} | ${d.domain} |`)
|
|
9146
|
+
];
|
|
9147
|
+
return lines.join("\n");
|
|
9148
|
+
}
|
|
9149
|
+
var init_seo_link_report = __esm({
|
|
9150
|
+
"src/api/seo-link-report.ts"() {
|
|
9151
|
+
"use strict";
|
|
9152
|
+
}
|
|
9153
|
+
});
|
|
9154
|
+
|
|
9057
9155
|
// src/api/seo-issues.ts
|
|
9058
9156
|
function dupes(pages, key) {
|
|
9059
9157
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -9168,6 +9266,143 @@ var init_seo_issues = __esm({
|
|
|
9168
9266
|
}
|
|
9169
9267
|
});
|
|
9170
9268
|
|
|
9269
|
+
// src/api/image-audit.ts
|
|
9270
|
+
function formatBytes(n) {
|
|
9271
|
+
if (n == null) return null;
|
|
9272
|
+
if (n < 1024) return `${n} B`;
|
|
9273
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
9274
|
+
return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
9275
|
+
}
|
|
9276
|
+
function collectUrls(pages) {
|
|
9277
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9278
|
+
for (const p of pages) for (const raw of p.imageLinks || []) {
|
|
9279
|
+
const u = decodeEntities(raw);
|
|
9280
|
+
if (!/^https?:\/\//i.test(u)) continue;
|
|
9281
|
+
if (/[{}]|%7[bd]/i.test(u)) continue;
|
|
9282
|
+
const k = dedupKey(u);
|
|
9283
|
+
const prev = byKey.get(k);
|
|
9284
|
+
if (!prev || /^https:/i.test(u) && /^http:/i.test(prev)) byKey.set(k, u);
|
|
9285
|
+
}
|
|
9286
|
+
return [...byKey.values()];
|
|
9287
|
+
}
|
|
9288
|
+
async function sizeAndType(url, timeoutMs) {
|
|
9289
|
+
const ctrl = new AbortController();
|
|
9290
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
9291
|
+
const read = (res) => ({
|
|
9292
|
+
len: res.headers.get("content-length"),
|
|
9293
|
+
cr: res.headers.get("content-range"),
|
|
9294
|
+
ct: res.headers.get("content-type"),
|
|
9295
|
+
status: res.status
|
|
9296
|
+
});
|
|
9297
|
+
try {
|
|
9298
|
+
let bytes = null;
|
|
9299
|
+
let ct = null;
|
|
9300
|
+
let status = null;
|
|
9301
|
+
try {
|
|
9302
|
+
const h = read(await fetch(url, { method: "HEAD", redirect: "follow", signal: ctrl.signal }));
|
|
9303
|
+
status = h.status;
|
|
9304
|
+
ct = h.ct;
|
|
9305
|
+
if (h.len) bytes = Number(h.len);
|
|
9306
|
+
} catch {
|
|
9307
|
+
}
|
|
9308
|
+
if (bytes == null) {
|
|
9309
|
+
const g = await fetch(url, { method: "GET", headers: { Range: "bytes=0-0" }, redirect: "follow", signal: ctrl.signal });
|
|
9310
|
+
const r = read(g);
|
|
9311
|
+
status = status ?? r.status;
|
|
9312
|
+
ct = ct ?? r.ct;
|
|
9313
|
+
if (r.cr && r.cr.includes("/")) {
|
|
9314
|
+
const tail = r.cr.split("/")[1];
|
|
9315
|
+
if (tail && tail !== "*") bytes = Number(tail);
|
|
9316
|
+
} else if (r.len) bytes = Number(r.len);
|
|
9317
|
+
try {
|
|
9318
|
+
await g.body?.cancel();
|
|
9319
|
+
} catch {
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
return { url, status, bytes: Number.isFinite(bytes) ? bytes : null, contentType: ct };
|
|
9323
|
+
} catch (e) {
|
|
9324
|
+
return { url, status: null, bytes: null, contentType: null, error: String(e.message || e) };
|
|
9325
|
+
} finally {
|
|
9326
|
+
clearTimeout(t);
|
|
9327
|
+
}
|
|
9328
|
+
}
|
|
9329
|
+
async function pool(items, n, fn) {
|
|
9330
|
+
const out = new Array(items.length);
|
|
9331
|
+
let i = 0;
|
|
9332
|
+
await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => {
|
|
9333
|
+
while (i < items.length) {
|
|
9334
|
+
const idx = i++;
|
|
9335
|
+
out[idx] = await fn(items[idx]);
|
|
9336
|
+
}
|
|
9337
|
+
}));
|
|
9338
|
+
return out;
|
|
9339
|
+
}
|
|
9340
|
+
async function auditImages(pages, opts = {}) {
|
|
9341
|
+
const concurrency = opts.concurrency ?? 12;
|
|
9342
|
+
const timeoutMs = opts.timeoutMs ?? 12e3;
|
|
9343
|
+
const urls = collectUrls(pages).slice(0, opts.max ?? 5e3);
|
|
9344
|
+
const heads = await pool(urls, concurrency, (u) => sizeAndType(u, timeoutMs));
|
|
9345
|
+
const rows = heads.map((r) => {
|
|
9346
|
+
const format = formatOf(r.contentType, r.url);
|
|
9347
|
+
return {
|
|
9348
|
+
...r,
|
|
9349
|
+
size: formatBytes(r.bytes),
|
|
9350
|
+
format,
|
|
9351
|
+
over100kb: r.bytes != null && r.bytes > OVER_BYTES,
|
|
9352
|
+
legacyFormat: format !== "unknown" && !MODERN.has(format)
|
|
9353
|
+
};
|
|
9354
|
+
});
|
|
9355
|
+
const sized = rows.filter((r) => r.bytes != null);
|
|
9356
|
+
const totalBytes = sized.reduce((a, r) => a + r.bytes, 0);
|
|
9357
|
+
const formatCounts = {};
|
|
9358
|
+
for (const r of rows) formatCounts[r.format] = (formatCounts[r.format] || 0) + 1;
|
|
9359
|
+
return {
|
|
9360
|
+
rows: rows.sort((a, b) => (b.bytes ?? 0) - (a.bytes ?? 0)),
|
|
9361
|
+
summary: {
|
|
9362
|
+
unique: rows.length,
|
|
9363
|
+
sized: sized.length,
|
|
9364
|
+
totalBytes,
|
|
9365
|
+
totalSize: formatBytes(totalBytes) ?? "0 B",
|
|
9366
|
+
avgSize: formatBytes(Math.round(totalBytes / (sized.length || 1))) ?? "0 B",
|
|
9367
|
+
over100kb: rows.filter((r) => r.over100kb).length,
|
|
9368
|
+
legacyFormat: rows.filter((r) => r.legacyFormat).length,
|
|
9369
|
+
formatCounts
|
|
9370
|
+
}
|
|
9371
|
+
};
|
|
9372
|
+
}
|
|
9373
|
+
function renderImageSection(audit) {
|
|
9374
|
+
const s = audit.summary;
|
|
9375
|
+
const fmt = Object.entries(s.formatCounts).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(" ");
|
|
9376
|
+
const heaviest = audit.rows.filter((r) => r.over100kb).slice(0, 15);
|
|
9377
|
+
const lines = [
|
|
9378
|
+
`## Images`,
|
|
9379
|
+
`**${s.unique} unique images** \xB7 ${s.totalSize} total \xB7 ${s.avgSize} avg \xB7 ${s.over100kb} over 100 KB \xB7 ${s.legacyFormat} legacy format`,
|
|
9380
|
+
`Formats: ${fmt}`
|
|
9381
|
+
];
|
|
9382
|
+
if (heaviest.length) {
|
|
9383
|
+
lines.push("", "| Size | Format | URL |", "|------|--------|-----|");
|
|
9384
|
+
for (const r of heaviest) lines.push(`| ${r.size} | ${r.format} | ${r.url} |`);
|
|
9385
|
+
}
|
|
9386
|
+
return lines.join("\n");
|
|
9387
|
+
}
|
|
9388
|
+
var OVER_BYTES, MODERN, decodeEntities, dedupKey, formatOf;
|
|
9389
|
+
var init_image_audit = __esm({
|
|
9390
|
+
"src/api/image-audit.ts"() {
|
|
9391
|
+
"use strict";
|
|
9392
|
+
OVER_BYTES = 100 * 1024;
|
|
9393
|
+
MODERN = /* @__PURE__ */ new Set(["webp", "avif", "svg", "svg+xml"]);
|
|
9394
|
+
decodeEntities = (u) => u.replace(/&/g, "&").replace(/�?38;/g, "&").replace(/&/gi, "&").trim();
|
|
9395
|
+
dedupKey = (u) => u.replace(/^https?:\/\//i, "//").replace(/\/$/, "");
|
|
9396
|
+
formatOf = (ct, url) => {
|
|
9397
|
+
if (ct) {
|
|
9398
|
+
const m = ct.split(";")[0].trim().toLowerCase();
|
|
9399
|
+
if (m.startsWith("image/")) return m.replace("image/", "");
|
|
9400
|
+
}
|
|
9401
|
+
return (url.split("?")[0].match(/\.([a-z0-9]+)$/i)?.[1] || "unknown").toLowerCase();
|
|
9402
|
+
};
|
|
9403
|
+
}
|
|
9404
|
+
});
|
|
9405
|
+
|
|
9171
9406
|
// src/api/blob-store.ts
|
|
9172
9407
|
function byteLength(data) {
|
|
9173
9408
|
return Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
|
|
@@ -9582,10 +9817,14 @@ function chunk(items, size) {
|
|
|
9582
9817
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
9583
9818
|
return out;
|
|
9584
9819
|
}
|
|
9585
|
-
function buildArtifacts(job, pages, branding) {
|
|
9820
|
+
function buildArtifacts(job, pages, branding, imageAudit) {
|
|
9586
9821
|
const { edges, metrics } = buildLinkGraph(pages, job.startUrl);
|
|
9587
9822
|
const issues = computeIssues(pages, metrics);
|
|
9588
|
-
const
|
|
9823
|
+
const linkReport = buildLinkReport(edges, [...metrics.values()], job.startUrl);
|
|
9824
|
+
let reportMd = renderIssueReport(job.startUrl, pages, issues, metrics);
|
|
9825
|
+
if (imageAudit) reportMd += `
|
|
9826
|
+
|
|
9827
|
+
${renderImageSection(imageAudit)}`;
|
|
9589
9828
|
const pageRows = pages.map((p) => {
|
|
9590
9829
|
const { bodyMarkdown: _b, schema: _s, outlinks: _o, ...rest } = p;
|
|
9591
9830
|
const m = metrics.get(p.url);
|
|
@@ -9597,8 +9836,15 @@ function buildArtifacts(job, pages, branding) {
|
|
|
9597
9836
|
{ name: "issues.json", body: JSON.stringify(issues, null, 2), type: "application/json" },
|
|
9598
9837
|
{ name: "pages.jsonl", body: jsonl(pageRows), type: "application/x-ndjson" },
|
|
9599
9838
|
{ name: "links.jsonl", body: jsonl(edges), type: "application/x-ndjson" },
|
|
9600
|
-
{ name: "link-metrics.jsonl", body: jsonl([...metrics.values()]), type: "application/x-ndjson" }
|
|
9839
|
+
{ name: "link-metrics.jsonl", body: jsonl([...metrics.values()]), type: "application/x-ndjson" },
|
|
9840
|
+
{ name: "link-report.md", body: renderLinkReport(linkReport), type: "text/markdown" },
|
|
9841
|
+
{ name: "links-summary.json", body: JSON.stringify(linkReport.summary, null, 2), type: "application/json" },
|
|
9842
|
+
{ name: "external-domains.json", body: JSON.stringify(linkReport.externalDomains, null, 2), type: "application/json" }
|
|
9601
9843
|
];
|
|
9844
|
+
if (imageAudit) {
|
|
9845
|
+
files.push({ name: "images.jsonl", body: jsonl(imageAudit.rows), type: "application/x-ndjson" });
|
|
9846
|
+
files.push({ name: "images-summary.json", body: JSON.stringify(imageAudit.summary, null, 2), type: "application/json" });
|
|
9847
|
+
}
|
|
9602
9848
|
if (branding) files.push({ name: "branding.json", body: JSON.stringify(branding, null, 2), type: "application/json" });
|
|
9603
9849
|
return files;
|
|
9604
9850
|
}
|
|
@@ -9609,7 +9855,9 @@ var init_site_extract = __esm({
|
|
|
9609
9855
|
init_client();
|
|
9610
9856
|
init_site_extractor();
|
|
9611
9857
|
init_seo_link_graph();
|
|
9858
|
+
init_seo_link_report();
|
|
9612
9859
|
init_seo_issues();
|
|
9860
|
+
init_image_audit();
|
|
9613
9861
|
init_blob_store();
|
|
9614
9862
|
init_screenshot();
|
|
9615
9863
|
init_browser_service_env();
|
|
@@ -9657,10 +9905,14 @@ var init_site_extract = __esm({
|
|
|
9657
9905
|
});
|
|
9658
9906
|
}
|
|
9659
9907
|
const branding = Array.isArray(job.options.formats) && job.options.formats.includes("branding") ? await step.run("branding", () => extractBranding(job.startUrl, key).catch(() => null)) : null;
|
|
9908
|
+
const imageAudit = Array.isArray(job.options.formats) && job.options.formats.includes("images") ? await step.run("image-audit", async () => {
|
|
9909
|
+
const pages = await getExtractedPages(jobId);
|
|
9910
|
+
return auditImages(pages, { concurrency: 12, timeoutMs: 12e3 });
|
|
9911
|
+
}) : null;
|
|
9660
9912
|
const artifacts = await step.run("finalize", async () => {
|
|
9661
9913
|
const pages = await getExtractedPages(jobId);
|
|
9662
9914
|
const store = getBlobStore();
|
|
9663
|
-
const files = buildArtifacts(job, pages, branding);
|
|
9915
|
+
const files = buildArtifacts(job, pages, branding, imageAudit);
|
|
9664
9916
|
const stored = [];
|
|
9665
9917
|
for (const f of files) stored.push(await store.put(`extract/${jobId}/${f.name}`, f.body, f.type));
|
|
9666
9918
|
await completeExtractJob(jobId, stored);
|
|
@@ -9935,7 +10187,7 @@ var init_catalog_seed = __esm({
|
|
|
9935
10187
|
kind: "simple",
|
|
9936
10188
|
endpoint: "/extract-site",
|
|
9937
10189
|
mcpName: "extract_site",
|
|
9938
|
-
tagline: "Map + bulk scrape
|
|
10190
|
+
tagline: "Map + bulk scrape (content)",
|
|
9939
10191
|
params: [
|
|
9940
10192
|
URL_PARAM,
|
|
9941
10193
|
{ key: "maxPages", label: "Max pages", type: "number", min: 1, max: 1e4, default: 100 },
|
|
@@ -9943,7 +10195,21 @@ var init_catalog_seed = __esm({
|
|
|
9943
10195
|
{ key: "rotateProxies", label: "Rotate proxies (blocked sites)", type: "boolean", default: true },
|
|
9944
10196
|
{ key: "background", label: "Run in background", type: "boolean", default: true, advanced: true }
|
|
9945
10197
|
],
|
|
9946
|
-
examples: ["https://steenshoney.com/ (full-site
|
|
10198
|
+
examples: ["https://steenshoney.com/ (full-site content crawl)"]
|
|
10199
|
+
},
|
|
10200
|
+
{
|
|
10201
|
+
key: "audit_site",
|
|
10202
|
+
label: "Audit",
|
|
10203
|
+
kind: "simple",
|
|
10204
|
+
endpoint: "/extract-site",
|
|
10205
|
+
mcpName: "audit_site",
|
|
10206
|
+
tagline: "Technical SEO audit (Screaming-Frog style)",
|
|
10207
|
+
params: [
|
|
10208
|
+
URL_PARAM,
|
|
10209
|
+
{ key: "maxPages", label: "Max pages", type: "number", min: 1, max: 1e4, default: 100 },
|
|
10210
|
+
{ key: "rotateProxies", label: "Rotate proxies (blocked sites)", type: "boolean", default: true }
|
|
10211
|
+
],
|
|
10212
|
+
examples: ["https://steenshoney.com/ (issues + link graph + image audit)"]
|
|
9947
10213
|
}
|
|
9948
10214
|
]
|
|
9949
10215
|
},
|
|
@@ -16037,7 +16303,7 @@ function saveFullReport(full) {
|
|
|
16037
16303
|
function reportSavingActive() {
|
|
16038
16304
|
return reportSavingEnabled && process.env.MCP_SCRAPER_SAVE_REPORTS !== "false";
|
|
16039
16305
|
}
|
|
16040
|
-
function saveBulkSite(siteUrl, pages, seo) {
|
|
16306
|
+
function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
16041
16307
|
if (!reportSavingActive()) return null;
|
|
16042
16308
|
try {
|
|
16043
16309
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -16060,15 +16326,35 @@ function saveBulkSite(siteUrl, pages, seo) {
|
|
|
16060
16326
|
(0, import_node_fs5.writeFileSync)((0, import_node_path7.join)(pagesDir, fname), content, "utf8");
|
|
16061
16327
|
return `| ${i + 1} | ${cell(p.title ?? "Untitled")} | ${p.url} | pages/${fname} |`;
|
|
16062
16328
|
});
|
|
16329
|
+
const dataFilesSection = seo ? [
|
|
16330
|
+
`
|
|
16331
|
+
## Data files (in this folder)`,
|
|
16332
|
+
`- \`report.md\` \u2014 SEO issues summary (counts + example URLs)`,
|
|
16333
|
+
`- \`issues.json\` \u2014 every finding with the full list of offender URLs`,
|
|
16334
|
+
`- \`pages.jsonl\` \u2014 per-page SEO fields, one JSON object per page`,
|
|
16335
|
+
`- \`links.jsonl\` \u2014 internal/external link edges (anchor, rel, target status)`,
|
|
16336
|
+
`- \`link-metrics.jsonl\` \u2014 inlinks, crawl depth, orphan flags per page`,
|
|
16337
|
+
`- \`link-report.md\` \u2014 link analysis: top internal pages by inlinks, top external domains, distribution`,
|
|
16338
|
+
`- \`links-summary.json\` \u2014 link totals + top-20 internal pages + top-20 external domains`,
|
|
16339
|
+
`- \`external-domains.json\` \u2014 every external domain linked to, ordered by link count`,
|
|
16340
|
+
...imageAudit ? [
|
|
16341
|
+
`- \`images.jsonl\` \u2014 every image with byte size, format, over-100KB and legacy-format flags`,
|
|
16342
|
+
`- \`images-summary.json\` \u2014 image totals (count, total weight, over-100KB, legacy)`
|
|
16343
|
+
] : [],
|
|
16344
|
+
...seo.branding ? [`- \`branding.json\` \u2014 site logo, colors, fonts`] : []
|
|
16345
|
+
].join("\n") : "";
|
|
16063
16346
|
const index = [
|
|
16064
16347
|
`# Site Extract: ${siteUrl}`,
|
|
16065
|
-
`**${pages.length} pages**
|
|
16348
|
+
`**${pages.length} pages** crawled. Everything is in this folder.`,
|
|
16349
|
+
`- Page content: one Markdown file per page under \`pages/\` (listed in the table below).`,
|
|
16350
|
+
seo ? `- SEO crawl data: the data files listed below.` : "",
|
|
16351
|
+
dataFilesSection,
|
|
16066
16352
|
`
|
|
16067
16353
|
## Pages
|
|
16068
16354
|
| # | Title | URL | File |
|
|
16069
16355
|
|---|-------|-----|------|
|
|
16070
16356
|
${indexRows.join("\n")}`
|
|
16071
|
-
].join("\n");
|
|
16357
|
+
].filter(Boolean).join("\n");
|
|
16072
16358
|
const indexFile = (0, import_node_path7.join)(dir, "index.md");
|
|
16073
16359
|
(0, import_node_fs5.writeFileSync)(indexFile, index, "utf8");
|
|
16074
16360
|
let seoFiles;
|
|
@@ -16083,8 +16369,23 @@ ${indexRows.join("\n")}`
|
|
|
16083
16369
|
(0, import_node_fs5.writeFileSync)(linksJsonl, toJsonl(seo.edges), "utf8");
|
|
16084
16370
|
(0, import_node_fs5.writeFileSync)(metricsJsonl, toJsonl(seo.metrics), "utf8");
|
|
16085
16371
|
(0, import_node_fs5.writeFileSync)(issuesFile, JSON.stringify(seo.issues, null, 2), "utf8");
|
|
16086
|
-
(0, import_node_fs5.writeFileSync)(reportFile, seo.reportMd
|
|
16087
|
-
|
|
16372
|
+
(0, import_node_fs5.writeFileSync)(reportFile, seo.reportMd + (imageAudit ? `
|
|
16373
|
+
|
|
16374
|
+
${renderImageSection(imageAudit)}` : ""), "utf8");
|
|
16375
|
+
const linkReportFile = (0, import_node_path7.join)(dir, "link-report.md");
|
|
16376
|
+
const linksSummaryFile = (0, import_node_path7.join)(dir, "links-summary.json");
|
|
16377
|
+
const externalDomainsFile = (0, import_node_path7.join)(dir, "external-domains.json");
|
|
16378
|
+
(0, import_node_fs5.writeFileSync)(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
|
|
16379
|
+
(0, import_node_fs5.writeFileSync)(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
|
|
16380
|
+
(0, import_node_fs5.writeFileSync)(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
|
|
16381
|
+
seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
|
|
16382
|
+
if (imageAudit) {
|
|
16383
|
+
const imagesJsonl = (0, import_node_path7.join)(dir, "images.jsonl");
|
|
16384
|
+
const imagesSummary = (0, import_node_path7.join)(dir, "images-summary.json");
|
|
16385
|
+
(0, import_node_fs5.writeFileSync)(imagesJsonl, imageAudit.rows.map((r) => JSON.stringify(r)).join("\n"), "utf8");
|
|
16386
|
+
(0, import_node_fs5.writeFileSync)(imagesSummary, JSON.stringify(imageAudit.summary, null, 2), "utf8");
|
|
16387
|
+
seoFiles.push(imagesJsonl, imagesSummary);
|
|
16388
|
+
}
|
|
16088
16389
|
if (seo.branding) {
|
|
16089
16390
|
const brandingFile = (0, import_node_path7.join)(dir, "branding.json");
|
|
16090
16391
|
(0, import_node_fs5.writeFileSync)(brandingFile, JSON.stringify(seo.branding, null, 2), "utf8");
|
|
@@ -16529,6 +16830,7 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
16529
16830
|
metrics: [...metrics.values()],
|
|
16530
16831
|
issues,
|
|
16531
16832
|
reportMd: renderIssueReport(siteUrl, pages, issues, metrics),
|
|
16833
|
+
linkReport: buildLinkReport(edges, [...metrics.values()], siteUrl),
|
|
16532
16834
|
branding: branding ?? void 0
|
|
16533
16835
|
};
|
|
16534
16836
|
}
|
|
@@ -16559,24 +16861,23 @@ function formatExtractSite(raw, input) {
|
|
|
16559
16861
|
durationMs: d.durationMs ?? 0
|
|
16560
16862
|
};
|
|
16561
16863
|
if (pages.length > BULK_PAGE_THRESHOLD) {
|
|
16562
|
-
const seo = buildSeoExport(input.url, pages, d.branding);
|
|
16563
16864
|
const bulk = saveBulkSite(input.url, pages.map((p) => ({
|
|
16564
16865
|
url: String(p.url ?? ""),
|
|
16565
16866
|
title: p.title ?? null,
|
|
16566
16867
|
bodyMarkdown: p.bodyMarkdown ?? null,
|
|
16567
16868
|
metaDescription: p.metaDescription ?? null,
|
|
16568
16869
|
schemaTypes: schemaTypesOf(p)
|
|
16569
|
-
}))
|
|
16870
|
+
})));
|
|
16570
16871
|
const preview = pages.slice(0, BULK_PAGE_THRESHOLD).map(pageRow).join("\n");
|
|
16571
|
-
const seoLine = bulk?.seoFiles?.length ? `
|
|
16572
|
-
- **SEO data:** \`report.md\` (issues summary), \`issues.json\` (findings + offender URLs), \`pages.jsonl\` (per-page fields), \`links.jsonl\` (link edges), \`link-metrics.jsonl\` (inlinks / crawl depth / orphans)` : "";
|
|
16573
16872
|
const location2 = bulk ? `
|
|
16574
16873
|
## \u{1F4C1} Bulk scrape saved
|
|
16575
|
-
- **Folder:** \`${bulk.dir}\`
|
|
16576
|
-
- **
|
|
16577
|
-
- **
|
|
16874
|
+
- **Folder:** \`${bulk.dir}\` \u2190 all scraped page content is here
|
|
16875
|
+
- **Start here:** \`${bulk.indexFile}\` \u2014 lists every page and its file
|
|
16876
|
+
- **Page content:** ${bulk.fileCount} Markdown files under \`pages/\` (one per page)
|
|
16877
|
+
|
|
16878
|
+
The page content is NOT inlined here to keep the context window clear \u2014 it is saved to disk. Open \`index.md\`, then open the page file you need from \`pages/\`.
|
|
16578
16879
|
|
|
16579
|
-
|
|
16880
|
+
\u{1F4A1} Want the full technical SEO audit (headings, link graph, indexability, image sizes/formats, issues)? Run \`audit_site\` on this URL instead.` : `
|
|
16580
16881
|
## \u26A0\uFE0F Bulk scrape not saved
|
|
16581
16882
|
Report saving is disabled in this environment, so the ${pages.length} pages of content could not be written to disk and are omitted to protect the context window. Enable \`MCP_SCRAPER_SAVE_REPORTS\` to capture bulk crawls.`;
|
|
16582
16883
|
const full2 = [
|
|
@@ -16620,6 +16921,62 @@ ${body}` : "_(no content extracted)_"
|
|
|
16620
16921
|
${pageDetails}${tips}`;
|
|
16621
16922
|
return { ...oneBlock(full, diskReport), structuredContent };
|
|
16622
16923
|
}
|
|
16924
|
+
async function formatAuditSite(raw, input) {
|
|
16925
|
+
const parsed = parseData(raw);
|
|
16926
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
16927
|
+
const d = parsed.data;
|
|
16928
|
+
const pages = d.pages ?? [];
|
|
16929
|
+
const schemaTypesOf = (p) => p.kpo?.type ?? (Array.isArray(p.schema) && p.schema.length ? [`${p.schema.length} block(s)`] : []);
|
|
16930
|
+
const seo = buildSeoExport(input.url, pages, d.branding);
|
|
16931
|
+
const imageAudit = await auditImages(pages, { concurrency: 12 });
|
|
16932
|
+
const bulk = saveBulkSite(input.url, pages.map((p) => ({
|
|
16933
|
+
url: String(p.url ?? ""),
|
|
16934
|
+
title: p.title ?? null,
|
|
16935
|
+
bodyMarkdown: p.bodyMarkdown ?? null,
|
|
16936
|
+
metaDescription: p.metaDescription ?? null,
|
|
16937
|
+
schemaTypes: schemaTypesOf(p)
|
|
16938
|
+
})), seo, imageAudit);
|
|
16939
|
+
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");
|
|
16940
|
+
const img = imageAudit.summary;
|
|
16941
|
+
const imgLine = `${img.unique} images \xB7 ${img.totalSize} total \xB7 ${img.over100kb} over 100 KB \xB7 ${img.legacyFormat} legacy format`;
|
|
16942
|
+
const lr = seo.linkReport.summary;
|
|
16943
|
+
const topExt = lr.external.topDomains.slice(0, 3).map((dm) => `${dm.domain} (${dm.links})`).join(", ") || "\u2014";
|
|
16944
|
+
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}`;
|
|
16945
|
+
const durationLine = `**${pages.length} pages** \xB7 ${((d.durationMs ?? 0) / 1e3).toFixed(1)}s`;
|
|
16946
|
+
const structuredContent = {
|
|
16947
|
+
url: input.url,
|
|
16948
|
+
pageCount: pages.length,
|
|
16949
|
+
durationMs: d.durationMs ?? 0,
|
|
16950
|
+
bulkFolder: bulk?.dir ?? null,
|
|
16951
|
+
issues: Object.fromEntries(Object.entries(seo.issues).map(([k, v]) => [k, v.count])),
|
|
16952
|
+
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
|
|
16953
|
+
links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
|
|
16954
|
+
};
|
|
16955
|
+
const location2 = bulk ? `
|
|
16956
|
+
## \u{1F4C1} Technical audit saved
|
|
16957
|
+
- **Folder:** \`${bulk.dir}\` \u2190 full audit + page content here
|
|
16958
|
+
- **Start here:** \`${bulk.indexFile}\` \u2014 maps every file in the folder
|
|
16959
|
+
- **Data:** \`report.md\` (issues + image audit), \`issues.json\`, \`pages.jsonl\` (per-page fields), \`links.jsonl\`, \`link-metrics.jsonl\`, \`images.jsonl\`
|
|
16960
|
+
|
|
16961
|
+
Detail is saved to disk, not inlined. Open \`report.md\` for the audit, \`pages.jsonl\` for per-page fields (headings, canonical, indexability), \`images.jsonl\` for image sizes/formats.` : `
|
|
16962
|
+
## \u26A0\uFE0F Audit not saved
|
|
16963
|
+
Report saving is disabled in this environment. Enable \`MCP_SCRAPER_SAVE_REPORTS\` to capture the technical audit.`;
|
|
16964
|
+
const full = [
|
|
16965
|
+
`# Technical SEO Audit: ${input.url}`,
|
|
16966
|
+
durationLine,
|
|
16967
|
+
location2,
|
|
16968
|
+
`
|
|
16969
|
+
## Top issues
|
|
16970
|
+
${topIssues || "_none found_"}`,
|
|
16971
|
+
`
|
|
16972
|
+
## Links
|
|
16973
|
+
${linkLine}`,
|
|
16974
|
+
`
|
|
16975
|
+
## Images
|
|
16976
|
+
${imgLine}`
|
|
16977
|
+
].join("\n");
|
|
16978
|
+
return { content: [{ type: "text", text: full }], structuredContent };
|
|
16979
|
+
}
|
|
16623
16980
|
function formatYoutubeHarvest(raw, input) {
|
|
16624
16981
|
const parsed = parseData(raw);
|
|
16625
16982
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -17804,7 +18161,9 @@ var init_mcp_response_formatter = __esm({
|
|
|
17804
18161
|
init_errors();
|
|
17805
18162
|
init_workflow_catalog();
|
|
17806
18163
|
init_seo_link_graph();
|
|
18164
|
+
init_seo_link_report();
|
|
17807
18165
|
init_seo_issues();
|
|
18166
|
+
init_image_audit();
|
|
17808
18167
|
reportSavingEnabled = true;
|
|
17809
18168
|
BULK_PAGE_THRESHOLD = 25;
|
|
17810
18169
|
BULK_URL_THRESHOLD = 500;
|
|
@@ -23339,7 +23698,7 @@ var PACKAGE_VERSION;
|
|
|
23339
23698
|
var init_version = __esm({
|
|
23340
23699
|
"src/version.ts"() {
|
|
23341
23700
|
"use strict";
|
|
23342
|
-
PACKAGE_VERSION = "0.3.
|
|
23701
|
+
PACKAGE_VERSION = "0.3.17";
|
|
23343
23702
|
}
|
|
23344
23703
|
});
|
|
23345
23704
|
|
|
@@ -23377,7 +23736,7 @@ NOTES
|
|
|
23377
23736
|
});
|
|
23378
23737
|
|
|
23379
23738
|
// src/mcp/mcp-tool-schemas.ts
|
|
23380
|
-
var import_zod27, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
|
|
23739
|
+
var import_zod27, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
|
|
23381
23740
|
var init_mcp_tool_schemas = __esm({
|
|
23382
23741
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
23383
23742
|
"use strict";
|
|
@@ -23408,12 +23767,18 @@ var init_mcp_tool_schemas = __esm({
|
|
|
23408
23767
|
maxUrls: import_zod27.z.number().int().min(1).max(1e4).optional().describe("Maximum URLs to discover. Use 100 for normal maps, up to 10000 for a full inventory. Large maps (over 500 URLs) write the complete inventory to a local file and return only a summary plus the file path instead of the full list inline.")
|
|
23409
23768
|
};
|
|
23410
23769
|
ExtractSiteInputSchema = {
|
|
23411
|
-
url: import_zod27.z.string().url().describe("Public website URL or domain to
|
|
23770
|
+
url: import_zod27.z.string().url().describe("Public website URL or domain to crawl for page CONTENT across multiple pages (map + scrape). Use when the user wants the content/text/markdown of a site's pages. For a technical SEO audit (issues, link graph, indexability, headings, image weights) use audit_site instead \u2014 extract_site returns content only, not analysis."),
|
|
23412
23771
|
maxPages: import_zod27.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract. Use 50 for a normal crawl, up to 10000 for a full-site bulk scrape. Bulk crawls (over 25 pages) switch to folder mode: every page is saved as its own Markdown file in a local folder and the response returns only a summary plus the folder path, so the full content never floods the context window."),
|
|
23413
23772
|
rotateProxies: import_zod27.z.boolean().optional().describe("Route page fetches through rotating residential proxies in headful browsers to defeat rate-limiting and bot blocks (403/429). Discovers URLs from the sitemap, fetches in batches with a fresh proxy per batch, retries failures on a new proxy, and automatically parallelizes across the account's concurrency slots. Slower and pricier than the default fetch path, so use only when a site blocks normal crawling."),
|
|
23414
23773
|
rotateProxyEvery: import_zod27.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, how many pages to fetch per browser/proxy before discarding it and minting a fresh one. Default 30. Lower values rotate IPs more aggressively against strict rate limits."),
|
|
23415
23774
|
formats: import_zod27.z.array(import_zod27.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Which data formats to include in the per-page output. markdown=page content as Markdown, links=internal/external link graph, json=structured data/schema, images=per-page image URLs (links only, no downloads), branding=site-level logo/colors/fonts captured once from the homepage (requires a browser, adds time). markdown, links, json, and images are always captured cheaply from the HTML; branding is the only opt-in capture. Defaults to markdown+links when omitted.")
|
|
23416
23775
|
};
|
|
23776
|
+
AuditSiteInputSchema = {
|
|
23777
|
+
url: import_zod27.z.string().url().describe("Public website URL or domain to run a full technical SEO audit on. Use when the user asks for a technical audit, SEO audit, site health check, or a Screaming-Frog-style crawl \u2014 i.e. they want ANALYSIS (issues, internal link graph, indexability, heading breakdown, image sizes/formats), not just page content. For plain content scraping use extract_site instead."),
|
|
23778
|
+
maxPages: import_zod27.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit. Use 50 for a normal audit, up to 10000 for a full-site audit. The audit always writes a folder of analysis files (report.md, issues.json, pages.jsonl, links.jsonl, link-metrics.jsonl, images.jsonl) plus per-page content, and returns a headline summary plus the folder path."),
|
|
23779
|
+
rotateProxies: import_zod27.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks (403/429). Slower and pricier than the default fetch path, so use only when a site blocks normal crawling."),
|
|
23780
|
+
rotateProxyEvery: import_zod27.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, how many pages to fetch per browser/proxy before discarding it and minting a fresh one. Default 30.")
|
|
23781
|
+
};
|
|
23417
23782
|
YoutubeHarvestInputSchema = {
|
|
23418
23783
|
mode: import_zod27.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
23419
23784
|
query: import_zod27.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
@@ -23714,6 +24079,26 @@ var init_mcp_tool_schemas = __esm({
|
|
|
23714
24079
|
})),
|
|
23715
24080
|
durationMs: import_zod27.z.number().min(0)
|
|
23716
24081
|
};
|
|
24082
|
+
AuditSiteOutputSchema = {
|
|
24083
|
+
url: import_zod27.z.string(),
|
|
24084
|
+
pageCount: import_zod27.z.number().int().min(0),
|
|
24085
|
+
durationMs: import_zod27.z.number().min(0),
|
|
24086
|
+
bulkFolder: import_zod27.z.string().nullable(),
|
|
24087
|
+
issues: import_zod27.z.record(import_zod27.z.string(), import_zod27.z.number()),
|
|
24088
|
+
images: import_zod27.z.object({
|
|
24089
|
+
unique: import_zod27.z.number().int().min(0),
|
|
24090
|
+
totalBytes: import_zod27.z.number().min(0),
|
|
24091
|
+
over100kb: import_zod27.z.number().int().min(0),
|
|
24092
|
+
legacyFormat: import_zod27.z.number().int().min(0)
|
|
24093
|
+
}),
|
|
24094
|
+
links: import_zod27.z.object({
|
|
24095
|
+
internal: import_zod27.z.number().int().min(0),
|
|
24096
|
+
external: import_zod27.z.number().int().min(0),
|
|
24097
|
+
orphans: import_zod27.z.number().int().min(0),
|
|
24098
|
+
brokenInternal: import_zod27.z.number().int().min(0),
|
|
24099
|
+
externalDomains: import_zod27.z.number().int().min(0)
|
|
24100
|
+
})
|
|
24101
|
+
};
|
|
23717
24102
|
MapsPlaceIntelOutputSchema = {
|
|
23718
24103
|
name: import_zod27.z.string(),
|
|
23719
24104
|
rating: NullableString,
|
|
@@ -24563,12 +24948,19 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
24563
24948
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
24564
24949
|
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
24565
24950
|
server.registerTool("extract_site", {
|
|
24566
|
-
title: "Multi-Page Site
|
|
24567
|
-
description: withReportNote("
|
|
24951
|
+
title: "Multi-Page Site Content Crawl",
|
|
24952
|
+
description: withReportNote("Crawl a public website and return the page CONTENT across multiple pages (map + scrape). Returns per-page titles and Markdown content. Supports up to maxPages=10000; bulk crawls over 25 pages switch to folder mode: each page is saved as its own Markdown file in a local folder and the response returns a summary plus the folder path instead of inlining all content. This tool returns content only \u2014 for a technical SEO audit (issues, internal link graph, indexability, heading breakdown, image sizes/formats) use audit_site. Use map_site_urls first when URL selection matters; use extract_url for one page."),
|
|
24568
24953
|
inputSchema: ExtractSiteInputSchema,
|
|
24569
24954
|
outputSchema: ExtractSiteOutputSchema,
|
|
24570
|
-
annotations: liveWebToolAnnotations("Multi-Page Site
|
|
24955
|
+
annotations: liveWebToolAnnotations("Multi-Page Site Content Crawl")
|
|
24571
24956
|
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
24957
|
+
server.registerTool("audit_site", {
|
|
24958
|
+
title: "Technical SEO Audit",
|
|
24959
|
+
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."),
|
|
24960
|
+
inputSchema: AuditSiteInputSchema,
|
|
24961
|
+
outputSchema: AuditSiteOutputSchema,
|
|
24962
|
+
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
24963
|
+
}, async (input) => formatAuditSite(await executor.auditSite(input), input));
|
|
24572
24964
|
server.registerTool("youtube_harvest", {
|
|
24573
24965
|
title: "YouTube Video Harvest",
|
|
24574
24966
|
description: withReportNote('Harvest YouTube video metadata when the user wants to find videos by topic, inspect a channel library, compare video angles, or get videoIds for later transcription. Use mode "search" for keyword/topic requests and mode "channel" for @handles, channel IDs, or channel URLs. Returns titles, views, durations, URLs, and videoIds for follow-up transcription. Use youtube_transcribe after selecting one video.'),
|
|
@@ -24906,6 +25298,9 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
24906
25298
|
extractSite(input) {
|
|
24907
25299
|
return this.call("/extract-site", input);
|
|
24908
25300
|
}
|
|
25301
|
+
auditSite(input) {
|
|
25302
|
+
return this.call("/extract-site", input);
|
|
25303
|
+
}
|
|
24909
25304
|
youtubeHarvest(input) {
|
|
24910
25305
|
return this.call("/youtube/harvest", input);
|
|
24911
25306
|
}
|