mcp-scraper 0.3.16 → 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.
Files changed (32) hide show
  1. package/dist/bin/api-server.cjs +133 -5
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +1 -1
  4. package/dist/bin/browser-agent-stdio-server.cjs +1 -1
  5. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  6. package/dist/bin/browser-agent-stdio-server.js +2 -2
  7. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  8. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-cli.js +1 -1
  10. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +121 -4
  11. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
  13. package/dist/bin/mcp-scraper-install.cjs +1 -1
  14. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  15. package/dist/bin/mcp-scraper-install.js +1 -1
  16. package/dist/bin/mcp-stdio-server.cjs +121 -4
  17. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  18. package/dist/bin/mcp-stdio-server.js +2 -2
  19. package/dist/{chunk-7XARJHS6.js → chunk-N33YEY6W.js} +124 -5
  20. package/dist/chunk-N33YEY6W.js.map +1 -0
  21. package/dist/chunk-PVWWY5NV.js +7 -0
  22. package/dist/chunk-PVWWY5NV.js.map +1 -0
  23. package/dist/{chunk-ISZWGIWL.js → chunk-UFD7N36R.js} +2 -2
  24. package/dist/{server-LPVBSE2E.js → server-TKWY47JA.js} +11 -5
  25. package/dist/server-TKWY47JA.js.map +1 -0
  26. package/docs/mcp-tool-manifest.generated.json +1 -1
  27. package/package.json +1 -1
  28. package/dist/chunk-7XARJHS6.js.map +0 -1
  29. package/dist/chunk-SOMBZK3V.js +0 -7
  30. package/dist/chunk-SOMBZK3V.js.map +0 -1
  31. package/dist/server-LPVBSE2E.js.map +0 -1
  32. /package/dist/{chunk-ISZWGIWL.js.map → chunk-UFD7N36R.js.map} +0 -0
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  HttpMcpToolExecutor,
4
4
  buildPaaExtractorMcpServer
5
- } from "../chunk-7XARJHS6.js";
5
+ } from "../chunk-N33YEY6W.js";
6
6
  import "../chunk-M2S27J6Z.js";
7
7
  import "../chunk-WN7PBKMV.js";
8
8
  import "../chunk-LFATOGDF.js";
9
- import "../chunk-SOMBZK3V.js";
9
+ import "../chunk-PVWWY5NV.js";
10
10
 
11
11
  // bin/mcp-stdio-server.ts
12
12
  import { readFileSync } from "fs";
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-LFATOGDF.js";
12
12
  import {
13
13
  PACKAGE_VERSION
14
- } from "./chunk-SOMBZK3V.js";
14
+ } from "./chunk-PVWWY5NV.js";
15
15
 
16
16
  // src/harvest-timeout.ts
17
17
  var VERCEL_FUNCTION_MAX_MS = 3e5;
@@ -256,6 +256,99 @@ function buildLinkGraph(pages, startUrl) {
256
256
  return { edges, metrics };
257
257
  }
258
258
 
259
+ // src/api/seo-link-report.ts
260
+ function registrableDomain(host) {
261
+ const h = host.replace(/^www\./, "").toLowerCase();
262
+ const parts = h.split(".");
263
+ return parts.length <= 2 ? h : parts.slice(-2).join(".");
264
+ }
265
+ function buildLinkReport(edges, metrics, siteUrl) {
266
+ let siteReg = "";
267
+ try {
268
+ siteReg = registrableDomain(new URL(siteUrl).hostname);
269
+ } catch {
270
+ siteReg = "";
271
+ }
272
+ const internalEdges = edges.filter((e) => e.internal);
273
+ const pages = metrics.length;
274
+ const orphans = metrics.filter((m) => m.orphan).length;
275
+ const brokenInternal = internalEdges.filter((e) => e.targetStatus != null && e.targetStatus >= 400).length;
276
+ const sumInlinks = metrics.reduce((a, m) => a + m.inlinks, 0);
277
+ const sumOutlinks = metrics.reduce((a, m) => a + m.outlinksInternal + m.outlinksExternal, 0);
278
+ const distribution = { zero: 0, oneToTwo: 0, threeToTen: 0, elevenPlus: 0 };
279
+ for (const m of metrics) {
280
+ if (m.inlinks === 0) distribution.zero++;
281
+ else if (m.inlinks <= 2) distribution.oneToTwo++;
282
+ else if (m.inlinks <= 10) distribution.threeToTen++;
283
+ else distribution.elevenPlus++;
284
+ }
285
+ 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 }));
286
+ const domMap = /* @__PURE__ */ new Map();
287
+ let externalTotal = 0;
288
+ for (const e of edges) {
289
+ let domain;
290
+ try {
291
+ domain = registrableDomain(new URL(e.to).hostname);
292
+ } catch {
293
+ continue;
294
+ }
295
+ if (!domain || domain === siteReg) continue;
296
+ externalTotal++;
297
+ const d = domMap.get(domain) ?? { links: 0, nofollow: 0, pages: /* @__PURE__ */ new Set() };
298
+ d.links++;
299
+ if (e.nofollow) d.nofollow++;
300
+ d.pages.add(e.from);
301
+ domMap.set(domain, d);
302
+ }
303
+ 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);
304
+ const round = (n) => Math.round(n * 10) / 10;
305
+ return {
306
+ summary: {
307
+ internal: {
308
+ totalLinks: internalEdges.length,
309
+ pages,
310
+ orphans,
311
+ brokenInternal,
312
+ avgInlinks: pages ? round(sumInlinks / pages) : 0,
313
+ avgOutlinks: pages ? round(sumOutlinks / pages) : 0,
314
+ distribution,
315
+ topByInlinks
316
+ },
317
+ external: {
318
+ totalLinks: externalTotal,
319
+ uniqueDomains: externalDomains.length,
320
+ topDomains: externalDomains.slice(0, 20)
321
+ }
322
+ },
323
+ externalDomains
324
+ };
325
+ }
326
+ function renderLinkReport(r) {
327
+ const s = r.summary;
328
+ const lines = [
329
+ `## Link analysis`,
330
+ `**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`,
331
+ `**External:** ${s.external.totalLinks} links to ${s.external.uniqueDomains} domains`,
332
+ ``,
333
+ `### Inlink distribution`,
334
+ `- 0 inlinks (orphans): ${s.internal.distribution.zero}`,
335
+ `- 1\u20132: ${s.internal.distribution.oneToTwo}`,
336
+ `- 3\u201310: ${s.internal.distribution.threeToTen}`,
337
+ `- 11+: ${s.internal.distribution.elevenPlus}`,
338
+ ``,
339
+ `### Top 20 internal pages by inlinks`,
340
+ `| inlinks | out (int/ext) | URL |`,
341
+ `|---|---|---|`,
342
+ ...s.internal.topByInlinks.map((p) => `| ${p.inlinks} | ${p.outlinksInternal}/${p.outlinksExternal} | ${p.url} |`),
343
+ ``,
344
+ `### Top 20 external domains by links`,
345
+ `| links | nofollow | from pages | domain |`,
346
+ `|---|---|---|---|`,
347
+ ...s.external.topDomains.map((d) => `| ${d.links} | ${d.nofollow} | ${d.pages} | ${d.domain} |`)
348
+ ];
349
+ return lines.join("\n");
350
+ }
351
+
259
352
  // src/api/seo-issues.ts
260
353
  var THIN_WORDS = 200;
261
354
  var TITLE_MAX = 60;
@@ -564,6 +657,9 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
564
657
  `- \`pages.jsonl\` \u2014 per-page SEO fields, one JSON object per page`,
565
658
  `- \`links.jsonl\` \u2014 internal/external link edges (anchor, rel, target status)`,
566
659
  `- \`link-metrics.jsonl\` \u2014 inlinks, crawl depth, orphan flags per page`,
660
+ `- \`link-report.md\` \u2014 link analysis: top internal pages by inlinks, top external domains, distribution`,
661
+ `- \`links-summary.json\` \u2014 link totals + top-20 internal pages + top-20 external domains`,
662
+ `- \`external-domains.json\` \u2014 every external domain linked to, ordered by link count`,
567
663
  ...imageAudit ? [
568
664
  `- \`images.jsonl\` \u2014 every image with byte size, format, over-100KB and legacy-format flags`,
569
665
  `- \`images-summary.json\` \u2014 image totals (count, total weight, over-100KB, legacy)`
@@ -599,7 +695,13 @@ ${indexRows.join("\n")}`
599
695
  writeFileSync(reportFile, seo.reportMd + (imageAudit ? `
600
696
 
601
697
  ${renderImageSection(imageAudit)}` : ""), "utf8");
602
- seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile];
698
+ const linkReportFile = join(dir, "link-report.md");
699
+ const linksSummaryFile = join(dir, "links-summary.json");
700
+ const externalDomainsFile = join(dir, "external-domains.json");
701
+ writeFileSync(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
702
+ writeFileSync(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
703
+ writeFileSync(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
704
+ seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
603
705
  if (imageAudit) {
604
706
  const imagesJsonl = join(dir, "images.jsonl");
605
707
  const imagesSummary = join(dir, "images-summary.json");
@@ -1051,6 +1153,7 @@ function buildSeoExport(siteUrl, pages, branding) {
1051
1153
  metrics: [...metrics.values()],
1052
1154
  issues,
1053
1155
  reportMd: renderIssueReport(siteUrl, pages, issues, metrics),
1156
+ linkReport: buildLinkReport(edges, [...metrics.values()], siteUrl),
1054
1157
  branding: branding ?? void 0
1055
1158
  };
1056
1159
  }
@@ -1159,6 +1262,9 @@ async function formatAuditSite(raw, input) {
1159
1262
  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
1263
  const img = imageAudit.summary;
1161
1264
  const imgLine = `${img.unique} images \xB7 ${img.totalSize} total \xB7 ${img.over100kb} over 100 KB \xB7 ${img.legacyFormat} legacy format`;
1265
+ const lr = seo.linkReport.summary;
1266
+ const topExt = lr.external.topDomains.slice(0, 3).map((dm) => `${dm.domain} (${dm.links})`).join(", ") || "\u2014";
1267
+ 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
1268
  const durationLine = `**${pages.length} pages** \xB7 ${((d.durationMs ?? 0) / 1e3).toFixed(1)}s`;
1163
1269
  const structuredContent = {
1164
1270
  url: input.url,
@@ -1166,7 +1272,8 @@ async function formatAuditSite(raw, input) {
1166
1272
  durationMs: d.durationMs ?? 0,
1167
1273
  bulkFolder: bulk?.dir ?? null,
1168
1274
  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 }
1275
+ images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
1276
+ links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
1170
1277
  };
1171
1278
  const location = bulk ? `
1172
1279
  ## \u{1F4C1} Technical audit saved
@@ -1185,6 +1292,9 @@ Report saving is disabled in this environment. Enable \`MCP_SCRAPER_SAVE_REPORTS
1185
1292
  ## Top issues
1186
1293
  ${topIssues || "_none found_"}`,
1187
1294
  `
1295
+ ## Links
1296
+ ${linkLine}`,
1297
+ `
1188
1298
  ## Images
1189
1299
  ${imgLine}`
1190
1300
  ].join("\n");
@@ -2715,6 +2825,13 @@ var AuditSiteOutputSchema = {
2715
2825
  totalBytes: z.number().min(0),
2716
2826
  over100kb: z.number().int().min(0),
2717
2827
  legacyFormat: z.number().int().min(0)
2828
+ }),
2829
+ links: z.object({
2830
+ internal: z.number().int().min(0),
2831
+ external: z.number().int().min(0),
2832
+ orphans: z.number().int().min(0),
2833
+ brokenInternal: z.number().int().min(0),
2834
+ externalDomains: z.number().int().min(0)
2718
2835
  })
2719
2836
  };
2720
2837
  var MapsPlaceIntelOutputSchema = {
@@ -3566,7 +3683,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
3566
3683
  }, async (input) => formatExtractSite(await executor.extractSite(input), input));
3567
3684
  server.registerTool("audit_site", {
3568
3685
  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 with inlinks/orphans/crawl-depth, 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, 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."),
3686
+ 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
3687
  inputSchema: AuditSiteInputSchema,
3571
3688
  outputSchema: AuditSiteOutputSchema,
3572
3689
  annotations: liveWebToolAnnotations("Technical SEO Audit")
@@ -3978,6 +4095,8 @@ var HttpMcpToolExecutor = class {
3978
4095
  export {
3979
4096
  harvestTimeoutBudget,
3980
4097
  buildLinkGraph,
4098
+ buildLinkReport,
4099
+ renderLinkReport,
3981
4100
  computeIssues,
3982
4101
  renderIssueReport,
3983
4102
  auditImages,
@@ -3996,4 +4115,4 @@ export {
3996
4115
  registerPaaExtractorMcpTools,
3997
4116
  HttpMcpToolExecutor
3998
4117
  };
3999
- //# sourceMappingURL=chunk-7XARJHS6.js.map
4118
+ //# sourceMappingURL=chunk-N33YEY6W.js.map