mcp-scraper 0.7.0 → 0.8.0

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.
@@ -22,6 +22,7 @@ import {
22
22
  import {
23
23
  HttpMcpToolExecutor,
24
24
  MemoryMcpToolExecutor,
25
+ buildLinkGraph,
25
26
  buildPaaExtractorMcpServer,
26
27
  configureReportSaving,
27
28
  exportFanout,
@@ -33,11 +34,13 @@ import {
33
34
  registerSerpIntelligenceCaptureTools,
34
35
  sanitizeAttempts,
35
36
  sanitizeHarvestResult
36
- } from "./chunk-YGRZU7IR.js";
37
+ } from "./chunk-ITJC3NN4.js";
37
38
  import {
38
39
  auditImages,
40
+ buildLinkReport,
41
+ computeIssues,
39
42
  getBlobStore
40
- } from "./chunk-GWRIO6JT.js";
43
+ } from "./chunk-R7EETU7Z.js";
41
44
  import {
42
45
  browserServiceApiKey,
43
46
  browserServiceProfileName,
@@ -67,7 +70,7 @@ import {
67
70
  RawMapsOverviewSchema,
68
71
  RawMapsReviewStatsSchema
69
72
  } from "./chunk-XGIPATLV.js";
70
- import "./chunk-EGWJ74EX.js";
73
+ import "./chunk-3MKSXDJ7.js";
71
74
  import {
72
75
  completeExtractJob,
73
76
  countSuccessfulPages,
@@ -4780,7 +4783,8 @@ async function spiderSite(opts) {
4780
4783
  totalFound: results.length,
4781
4784
  durationMs: Date.now() - startMs,
4782
4785
  truncated: queue.length > 0 || results.length >= maxUrls,
4783
- browserRetries
4786
+ browserRetries,
4787
+ sitemapUrls: sitemapUrls.slice(0, maxUrls)
4784
4788
  };
4785
4789
  }
4786
4790
 
@@ -4904,6 +4908,23 @@ async function crawlWithRotation(urls, opts) {
4904
4908
  return urls.map((u) => byUrl.get(u) ?? { url: u, html: null, status: 0, via: "browser" });
4905
4909
  }
4906
4910
 
4911
+ // src/api/seo-audit.ts
4912
+ function buildSeoAudit(pages, startUrl) {
4913
+ const { edges, metrics } = buildLinkGraph(pages, startUrl);
4914
+ const issues = computeIssues(pages, metrics);
4915
+ const { summary } = buildLinkReport(edges, [...metrics.values()], startUrl);
4916
+ return {
4917
+ issues,
4918
+ linkSummary: summary,
4919
+ pageMetrics: [...metrics.values()].map((m) => ({
4920
+ url: m.url,
4921
+ inboundInternal: m.inlinks,
4922
+ outboundInternal: m.outlinksInternal,
4923
+ orphan: m.orphan
4924
+ }))
4925
+ };
4926
+ }
4927
+
4907
4928
  // src/api/site-extractor.ts
4908
4929
  var PIXEL_WIDTHS = { i: 4, l: 4, j: 4, ".": 4, ",": 4, "'": 4, t: 6, f: 6, r: 6, " ": 4, m: 14, w: 13, W: 16, M: 16 };
4909
4930
  function estimatePixels(s) {
@@ -4947,6 +4968,7 @@ function emptyPageData(url, status, via) {
4947
4968
  imageLinks: [],
4948
4969
  indexable: status === 200,
4949
4970
  indexabilityReason: status === 200 ? null : "non-200",
4971
+ inSitemap: null,
4950
4972
  schemaTypes: [],
4951
4973
  canonicalUrl: null,
4952
4974
  internalLinks: 0,
@@ -4963,7 +4985,8 @@ function makeSeedSpider(startUrl, urls) {
4963
4985
  totalFound: urls.length,
4964
4986
  durationMs: 0,
4965
4987
  truncated: false,
4966
- browserRetries: 0
4988
+ browserRetries: 0,
4989
+ sitemapUrls: []
4967
4990
  };
4968
4991
  }
4969
4992
  var UA2 = "Mozilla/5.0 (compatible; ThorbitBot/1.0; +https://thorbit.ai)";
@@ -5134,6 +5157,7 @@ function parsePageData(url, html, status, via, resp) {
5134
5157
  imageLinks,
5135
5158
  indexable,
5136
5159
  indexabilityReason,
5160
+ inSitemap: null,
5137
5161
  schemaTypes,
5138
5162
  canonicalUrl,
5139
5163
  internalLinks,
@@ -5147,11 +5171,11 @@ async function extractPagesRotating(urls, opts) {
5147
5171
  const fetched = await crawlWithRotation(urls, { apiKey: opts.kernelApiKey, concurrency: opts.concurrency, urlsPerBrowser: opts.urlsPerBrowser });
5148
5172
  return fetched.map((r) => r.html ? parsePageData(r.url, r.html, r.status || 200, "browser", { headers: r.headers, responseTimeMs: r.responseTimeMs, redirectUrl: r.redirectUrl }) : emptyPageData(r.url, r.status || null, "browser"));
5149
5173
  }
5150
- async function discoverUrls(startUrl, maxPages, kernelApiKey) {
5174
+ async function discoverUrlsWithSitemap(startUrl, maxPages, kernelApiKey) {
5151
5175
  const sitemapUrls = await discoverSitemapUrls2(startUrl, maxPages);
5152
- if (sitemapUrls.length > 0) return sitemapUrls;
5176
+ if (sitemapUrls.length > 0) return { urls: sitemapUrls, sitemapUrls };
5153
5177
  const spider = await spiderSite({ startUrl, maxUrls: maxPages, concurrency: 12, kernelApiKey });
5154
- return spider.urls.filter((u) => u.status === 200 || u.status === null).slice(0, maxPages).map((u) => u.url);
5178
+ return { urls: spider.urls.filter((u) => u.status === 200 || u.status === null).slice(0, maxPages).map((u) => u.url), sitemapUrls: spider.sitemapUrls };
5155
5179
  }
5156
5180
  async function fetchAndParse(url, kernelApiKey) {
5157
5181
  const startedAt = Date.now();
@@ -5210,6 +5234,7 @@ async function extractSite(opts) {
5210
5234
  let browserRetries = 0;
5211
5235
  let truncated = false;
5212
5236
  let spider;
5237
+ let sitemapUrls = [];
5213
5238
  if (rotating) {
5214
5239
  const browserConcurrency = Math.max(1, Math.min(opts.parallelism ?? 1, 8));
5215
5240
  const urlsPerBrowser = opts.rotateProxyEvery ?? 10;
@@ -5227,7 +5252,8 @@ async function extractSite(opts) {
5227
5252
  for (const u of opts.seedUrls) enqueue(u);
5228
5253
  } else {
5229
5254
  enqueue(opts.startUrl);
5230
- for (const u of await discoverSitemapUrls2(opts.startUrl, maxPages)) enqueue(u);
5255
+ sitemapUrls = await discoverSitemapUrls2(opts.startUrl, maxPages);
5256
+ for (const u of sitemapUrls) enqueue(u);
5231
5257
  }
5232
5258
  const waveCap = Math.max(browserConcurrency * urlsPerBrowser, 30);
5233
5259
  let done = 0;
@@ -5256,6 +5282,7 @@ async function extractSite(opts) {
5256
5282
  spider = makeSeedSpider(opts.startUrl, [...seen]);
5257
5283
  } else {
5258
5284
  spider = seeded ? makeSeedSpider(opts.startUrl, opts.seedUrls) : await spiderSite({ startUrl: opts.startUrl, maxUrls: maxPages, concurrency: 12, kernelApiKey: opts.kernelApiKey });
5285
+ sitemapUrls = spider.sitemapUrls;
5259
5286
  const urlsToExtract = spider.urls.filter((u) => u.status === 200 || u.status === null).slice(0, maxPages).map((u) => u.url);
5260
5287
  browserRetries = spider.browserRetries;
5261
5288
  await runWithConcurrency(urlsToExtract, concurrency, async (url) => {
@@ -5265,10 +5292,17 @@ async function extractSite(opts) {
5265
5292
  });
5266
5293
  truncated = spider.truncated || pages.length >= maxPages;
5267
5294
  }
5295
+ const sitemapSet = sitemapUrls.length > 0 ? new Set(sitemapUrls.map((u) => normalizeUrl(u, opts.startUrl) ?? u)) : null;
5296
+ for (const p of pages) p.inSitemap = sitemapSet ? sitemapSet.has(normalizeUrl(p.url, opts.startUrl) ?? p.url) : null;
5268
5297
  let branding = null;
5269
5298
  if (opts.formats?.includes("branding") && opts.kernelApiKey) {
5270
5299
  branding = await extractBranding(opts.startUrl, opts.kernelApiKey).catch(() => null);
5271
5300
  }
5301
+ const seoAudit = opts.formats?.includes("issues") ? buildSeoAudit(pages, opts.startUrl) : null;
5302
+ let imageAudit = null;
5303
+ if (opts.formats?.includes("images")) {
5304
+ imageAudit = await auditImages(pages, { concurrency: 12, timeoutMs: 12e3 });
5305
+ }
5272
5306
  return {
5273
5307
  startUrl: opts.startUrl,
5274
5308
  pages,
@@ -5277,7 +5311,9 @@ async function extractSite(opts) {
5277
5311
  durationMs: Date.now() - startMs,
5278
5312
  truncated,
5279
5313
  browserRetries,
5280
- branding
5314
+ branding,
5315
+ seoAudit,
5316
+ imageAudit
5281
5317
  };
5282
5318
  }
5283
5319
 
@@ -7519,12 +7555,13 @@ var siteExtractFn = inngest.createFunction(
7519
7555
  const maxPages = Number(job.options.maxPages ?? 1e4);
7520
7556
  const concurrency = Number(job.options.concurrency ?? 1);
7521
7557
  const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
7522
- const seed = await step.run("discover", async () => {
7523
- const discovered = await discoverUrls(job.startUrl, maxPages, key);
7524
- const out = discovered.length ? discovered : [job.startUrl];
7525
- await setExtractJobTotal(jobId, out.length);
7526
- return out;
7558
+ const seedRaw = await step.run("discover", async () => {
7559
+ const discovered = await discoverUrlsWithSitemap(job.startUrl, maxPages, key);
7560
+ const urls = discovered.urls.length ? discovered.urls : [job.startUrl];
7561
+ await setExtractJobTotal(jobId, urls.length);
7562
+ return { urls, sitemapUrls: discovered.sitemapUrls };
7527
7563
  });
7564
+ const seed = Array.isArray(seedRaw) ? { urls: seedRaw, sitemapUrls: [] } : seedRaw;
7528
7565
  const siteHost = (() => {
7529
7566
  try {
7530
7567
  return new URL(job.startUrl).hostname.replace(/^www\./, "");
@@ -7548,8 +7585,9 @@ var siteExtractFn = inngest.createFunction(
7548
7585
  return false;
7549
7586
  }
7550
7587
  };
7551
- const queue = [...seed];
7552
- const seen = new Set(seed.map(norm));
7588
+ const sitemapSet = seed.sitemapUrls.length > 0 ? new Set(seed.sitemapUrls.map(norm)) : null;
7589
+ const queue = [...seed.urls];
7590
+ const seen = new Set(seed.urls.map(norm));
7553
7591
  let crawled = 0;
7554
7592
  const MAX_BATCHES = Math.ceil(maxPages / BATCH) + 30;
7555
7593
  for (let i = 0; i < MAX_BATCHES && crawled < maxPages && crawled < queue.length; i++) {
@@ -7557,7 +7595,7 @@ var siteExtractFn = inngest.createFunction(
7557
7595
  if (!batch.length) break;
7558
7596
  const newLinks = await step.run(`crawl-batch-${i}`, async () => {
7559
7597
  const pages = await extractPagesRotating(batch, { kernelApiKey: key, concurrency, urlsPerBrowser });
7560
- await saveExtractPages(jobId, pages);
7598
+ await saveExtractPages(jobId, pages.map((p) => ({ ...p, inSitemap: sitemapSet ? sitemapSet.has(norm(p.url)) : null })));
7561
7599
  const found = [];
7562
7600
  for (const p of pages) for (const l of p.outlinks ?? []) {
7563
7601
  if (l.internal && l.href && sameSite(l.href)) found.push(l.href);
@@ -7581,9 +7619,13 @@ var siteExtractFn = inngest.createFunction(
7581
7619
  const pages = await getExtractedPages(jobId);
7582
7620
  return auditImages(pages, { concurrency: 12, timeoutMs: 12e3 });
7583
7621
  }) : null;
7622
+ const seoAudit = Array.isArray(job.options.formats) && job.options.formats.includes("issues") ? await step.run("seo-audit", async () => {
7623
+ const pages = await getExtractedPages(jobId);
7624
+ return buildSeoAudit(pages, job.startUrl);
7625
+ }) : null;
7584
7626
  const artifacts = await step.run("finalize", async () => {
7585
- const { assembleExtractArtifacts } = await import("./extract-bundle-COS56ZDO.js");
7586
- const stored = await assembleExtractArtifacts(job, { branding, imageAudit });
7627
+ const { assembleExtractArtifacts } = await import("./extract-bundle-U4D5LW5W.js");
7628
+ const stored = await assembleExtractArtifacts(job, { branding, imageAudit, seoAudit });
7587
7629
  await completeExtractJob(jobId, stored);
7588
7630
  return stored;
7589
7631
  });
@@ -9089,7 +9131,7 @@ var ExtractSiteBodySchema = z11.object({
9089
9131
  rotateProxies: z11.boolean().optional(),
9090
9132
  rotateProxyEvery: z11.number().int().min(1).max(100).optional(),
9091
9133
  background: z11.boolean().optional(),
9092
- formats: z11.array(z11.enum(["markdown", "links", "json", "images", "branding"])).optional()
9134
+ formats: z11.array(z11.enum(["markdown", "links", "json", "images", "branding", "issues"])).optional()
9093
9135
  });
9094
9136
  var YoutubeHarvestBodySchema = z11.object({
9095
9137
  mode: z11.enum(["search", "channel"]),
@@ -20458,6 +20500,7 @@ app.use("*", async (c, next) => {
20458
20500
  });
20459
20501
  function opForCostPath(p) {
20460
20502
  if (p === "/harvest" || p === "/harvest/sync") return "harvest";
20503
+ if (p === "/diff-page") return "diff_page";
20461
20504
  if (p === "/extract-url") return "page_scrape";
20462
20505
  if (p === "/extract-site") return "extract_site";
20463
20506
  if (p === "/map-urls") return "url_map";
@@ -20630,6 +20673,13 @@ app.get("/catalog", (c) => {
20630
20673
  c.header("Cache-Control", "public, max-age=60");
20631
20674
  return c.json(CATALOG);
20632
20675
  });
20676
+ app.get("/rates", (c) => {
20677
+ const costs = CREDIT_COST_CATALOG.map(({ aliases, ...cost }) => ({
20678
+ ...cost,
20679
+ ...cost.notes ? { notes: cost.notes.replace(/ via fal\.ai/g, "") } : {}
20680
+ }));
20681
+ return c.json({ creditsPerDollarStarter: Math.round(1 / 15e-5), costs });
20682
+ });
20633
20683
  app.get("/me", async (c) => {
20634
20684
  const token = getCookie2(c, "session");
20635
20685
  if (!token) return c.json({ authenticated: false });
@@ -21905,7 +21955,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
21905
21955
  }
21906
21956
  const jobId = c.req.param("id");
21907
21957
  const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-NVSZH35Y.js");
21908
- const { assembleExtractArtifacts } = await import("./extract-bundle-COS56ZDO.js");
21958
+ const { assembleExtractArtifacts } = await import("./extract-bundle-U4D5LW5W.js");
21909
21959
  const job = await getExtractJob2(jobId);
21910
21960
  if (!job) return c.json({ error: "job not found" }, 404);
21911
21961
  const stored = await assembleExtractArtifacts(job, {});
@@ -22076,4 +22126,4 @@ app.get("/blog/:slug/", (c) => {
22076
22126
  export {
22077
22127
  app
22078
22128
  };
22079
- //# sourceMappingURL=server-JNY4XPZE.js.map
22129
+ //# sourceMappingURL=server-WBDVVHR6.js.map