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
@@ -10,7 +10,7 @@ import {
10
10
  import "../chunk-LFATOGDF.js";
11
11
  import {
12
12
  PACKAGE_VERSION
13
- } from "../chunk-SOMBZK3V.js";
13
+ } from "../chunk-PVWWY5NV.js";
14
14
 
15
15
  // src/cli/human-cli.ts
16
16
  import { Command } from "commander";
@@ -456,7 +456,7 @@ render();
456
456
  }
457
457
 
458
458
  // src/version.ts
459
- var PACKAGE_VERSION = "0.3.16";
459
+ var PACKAGE_VERSION = "0.3.17";
460
460
 
461
461
  // src/mcp/browser-agent-tool-schemas.ts
462
462
  var import_zod = require("zod");
@@ -1970,6 +1970,99 @@ function buildLinkGraph(pages, startUrl) {
1970
1970
  return { edges, metrics };
1971
1971
  }
1972
1972
 
1973
+ // src/api/seo-link-report.ts
1974
+ function registrableDomain(host) {
1975
+ const h = host.replace(/^www\./, "").toLowerCase();
1976
+ const parts = h.split(".");
1977
+ return parts.length <= 2 ? h : parts.slice(-2).join(".");
1978
+ }
1979
+ function buildLinkReport(edges, metrics, siteUrl) {
1980
+ let siteReg = "";
1981
+ try {
1982
+ siteReg = registrableDomain(new URL(siteUrl).hostname);
1983
+ } catch {
1984
+ siteReg = "";
1985
+ }
1986
+ const internalEdges = edges.filter((e) => e.internal);
1987
+ const pages = metrics.length;
1988
+ const orphans = metrics.filter((m) => m.orphan).length;
1989
+ const brokenInternal = internalEdges.filter((e) => e.targetStatus != null && e.targetStatus >= 400).length;
1990
+ const sumInlinks = metrics.reduce((a, m) => a + m.inlinks, 0);
1991
+ const sumOutlinks = metrics.reduce((a, m) => a + m.outlinksInternal + m.outlinksExternal, 0);
1992
+ const distribution = { zero: 0, oneToTwo: 0, threeToTen: 0, elevenPlus: 0 };
1993
+ for (const m of metrics) {
1994
+ if (m.inlinks === 0) distribution.zero++;
1995
+ else if (m.inlinks <= 2) distribution.oneToTwo++;
1996
+ else if (m.inlinks <= 10) distribution.threeToTen++;
1997
+ else distribution.elevenPlus++;
1998
+ }
1999
+ 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 }));
2000
+ const domMap = /* @__PURE__ */ new Map();
2001
+ let externalTotal = 0;
2002
+ for (const e of edges) {
2003
+ let domain;
2004
+ try {
2005
+ domain = registrableDomain(new URL(e.to).hostname);
2006
+ } catch {
2007
+ continue;
2008
+ }
2009
+ if (!domain || domain === siteReg) continue;
2010
+ externalTotal++;
2011
+ const d = domMap.get(domain) ?? { links: 0, nofollow: 0, pages: /* @__PURE__ */ new Set() };
2012
+ d.links++;
2013
+ if (e.nofollow) d.nofollow++;
2014
+ d.pages.add(e.from);
2015
+ domMap.set(domain, d);
2016
+ }
2017
+ 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);
2018
+ const round = (n) => Math.round(n * 10) / 10;
2019
+ return {
2020
+ summary: {
2021
+ internal: {
2022
+ totalLinks: internalEdges.length,
2023
+ pages,
2024
+ orphans,
2025
+ brokenInternal,
2026
+ avgInlinks: pages ? round(sumInlinks / pages) : 0,
2027
+ avgOutlinks: pages ? round(sumOutlinks / pages) : 0,
2028
+ distribution,
2029
+ topByInlinks
2030
+ },
2031
+ external: {
2032
+ totalLinks: externalTotal,
2033
+ uniqueDomains: externalDomains.length,
2034
+ topDomains: externalDomains.slice(0, 20)
2035
+ }
2036
+ },
2037
+ externalDomains
2038
+ };
2039
+ }
2040
+ function renderLinkReport(r) {
2041
+ const s = r.summary;
2042
+ const lines = [
2043
+ `## Link analysis`,
2044
+ `**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`,
2045
+ `**External:** ${s.external.totalLinks} links to ${s.external.uniqueDomains} domains`,
2046
+ ``,
2047
+ `### Inlink distribution`,
2048
+ `- 0 inlinks (orphans): ${s.internal.distribution.zero}`,
2049
+ `- 1\u20132: ${s.internal.distribution.oneToTwo}`,
2050
+ `- 3\u201310: ${s.internal.distribution.threeToTen}`,
2051
+ `- 11+: ${s.internal.distribution.elevenPlus}`,
2052
+ ``,
2053
+ `### Top 20 internal pages by inlinks`,
2054
+ `| inlinks | out (int/ext) | URL |`,
2055
+ `|---|---|---|`,
2056
+ ...s.internal.topByInlinks.map((p) => `| ${p.inlinks} | ${p.outlinksInternal}/${p.outlinksExternal} | ${p.url} |`),
2057
+ ``,
2058
+ `### Top 20 external domains by links`,
2059
+ `| links | nofollow | from pages | domain |`,
2060
+ `|---|---|---|---|`,
2061
+ ...s.external.topDomains.map((d) => `| ${d.links} | ${d.nofollow} | ${d.pages} | ${d.domain} |`)
2062
+ ];
2063
+ return lines.join("\n");
2064
+ }
2065
+
1973
2066
  // src/api/seo-issues.ts
1974
2067
  var THIN_WORDS = 200;
1975
2068
  var TITLE_MAX = 60;
@@ -2275,6 +2368,9 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
2275
2368
  `- \`pages.jsonl\` \u2014 per-page SEO fields, one JSON object per page`,
2276
2369
  `- \`links.jsonl\` \u2014 internal/external link edges (anchor, rel, target status)`,
2277
2370
  `- \`link-metrics.jsonl\` \u2014 inlinks, crawl depth, orphan flags per page`,
2371
+ `- \`link-report.md\` \u2014 link analysis: top internal pages by inlinks, top external domains, distribution`,
2372
+ `- \`links-summary.json\` \u2014 link totals + top-20 internal pages + top-20 external domains`,
2373
+ `- \`external-domains.json\` \u2014 every external domain linked to, ordered by link count`,
2278
2374
  ...imageAudit ? [
2279
2375
  `- \`images.jsonl\` \u2014 every image with byte size, format, over-100KB and legacy-format flags`,
2280
2376
  `- \`images-summary.json\` \u2014 image totals (count, total weight, over-100KB, legacy)`
@@ -2310,7 +2406,13 @@ ${indexRows.join("\n")}`
2310
2406
  (0, import_node_fs3.writeFileSync)(reportFile, seo.reportMd + (imageAudit ? `
2311
2407
 
2312
2408
  ${renderImageSection(imageAudit)}` : ""), "utf8");
2313
- seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile];
2409
+ const linkReportFile = (0, import_node_path4.join)(dir, "link-report.md");
2410
+ const linksSummaryFile = (0, import_node_path4.join)(dir, "links-summary.json");
2411
+ const externalDomainsFile = (0, import_node_path4.join)(dir, "external-domains.json");
2412
+ (0, import_node_fs3.writeFileSync)(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
2413
+ (0, import_node_fs3.writeFileSync)(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
2414
+ (0, import_node_fs3.writeFileSync)(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
2415
+ seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
2314
2416
  if (imageAudit) {
2315
2417
  const imagesJsonl = (0, import_node_path4.join)(dir, "images.jsonl");
2316
2418
  const imagesSummary = (0, import_node_path4.join)(dir, "images-summary.json");
@@ -2762,6 +2864,7 @@ function buildSeoExport(siteUrl, pages, branding) {
2762
2864
  metrics: [...metrics.values()],
2763
2865
  issues,
2764
2866
  reportMd: renderIssueReport(siteUrl, pages, issues, metrics),
2867
+ linkReport: buildLinkReport(edges, [...metrics.values()], siteUrl),
2765
2868
  branding: branding ?? void 0
2766
2869
  };
2767
2870
  }
@@ -2870,6 +2973,9 @@ async function formatAuditSite(raw, input) {
2870
2973
  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");
2871
2974
  const img = imageAudit.summary;
2872
2975
  const imgLine = `${img.unique} images \xB7 ${img.totalSize} total \xB7 ${img.over100kb} over 100 KB \xB7 ${img.legacyFormat} legacy format`;
2976
+ const lr = seo.linkReport.summary;
2977
+ const topExt = lr.external.topDomains.slice(0, 3).map((dm) => `${dm.domain} (${dm.links})`).join(", ") || "\u2014";
2978
+ 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}`;
2873
2979
  const durationLine = `**${pages.length} pages** \xB7 ${((d.durationMs ?? 0) / 1e3).toFixed(1)}s`;
2874
2980
  const structuredContent = {
2875
2981
  url: input.url,
@@ -2877,7 +2983,8 @@ async function formatAuditSite(raw, input) {
2877
2983
  durationMs: d.durationMs ?? 0,
2878
2984
  bulkFolder: bulk?.dir ?? null,
2879
2985
  issues: Object.fromEntries(Object.entries(seo.issues).map(([k, v]) => [k, v.count])),
2880
- images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat }
2986
+ images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
2987
+ links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
2881
2988
  };
2882
2989
  const location = bulk ? `
2883
2990
  ## \u{1F4C1} Technical audit saved
@@ -2896,6 +3003,9 @@ Report saving is disabled in this environment. Enable \`MCP_SCRAPER_SAVE_REPORTS
2896
3003
  ## Top issues
2897
3004
  ${topIssues || "_none found_"}`,
2898
3005
  `
3006
+ ## Links
3007
+ ${linkLine}`,
3008
+ `
2899
3009
  ## Images
2900
3010
  ${imgLine}`
2901
3011
  ].join("\n");
@@ -4463,6 +4573,13 @@ var AuditSiteOutputSchema = {
4463
4573
  totalBytes: import_zod3.z.number().min(0),
4464
4574
  over100kb: import_zod3.z.number().int().min(0),
4465
4575
  legacyFormat: import_zod3.z.number().int().min(0)
4576
+ }),
4577
+ links: import_zod3.z.object({
4578
+ internal: import_zod3.z.number().int().min(0),
4579
+ external: import_zod3.z.number().int().min(0),
4580
+ orphans: import_zod3.z.number().int().min(0),
4581
+ brokenInternal: import_zod3.z.number().int().min(0),
4582
+ externalDomains: import_zod3.z.number().int().min(0)
4466
4583
  })
4467
4584
  };
4468
4585
  var MapsPlaceIntelOutputSchema = {
@@ -5309,7 +5426,7 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
5309
5426
  }, async (input) => formatExtractSite(await executor.extractSite(input), input));
5310
5427
  server2.registerTool("audit_site", {
5311
5428
  title: "Technical SEO Audit",
5312
- 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."),
5429
+ 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."),
5313
5430
  inputSchema: AuditSiteInputSchema,
5314
5431
  outputSchema: AuditSiteOutputSchema,
5315
5432
  annotations: liveWebToolAnnotations("Technical SEO Audit")