mcp-scraper 0.5.0 → 0.7.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.
Files changed (52) hide show
  1. package/dist/bin/api-server.cjs +675 -214
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +2 -2
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +322 -11
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +3 -3
  13. package/dist/bin/paa-harvest.cjs +166 -74
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +1 -1
  16. package/dist/chunk-EGWJ74EX.js +7 -0
  17. package/dist/chunk-EGWJ74EX.js.map +1 -0
  18. package/dist/{chunk-APJO2XV5.js → chunk-HL33CGJF.js} +3 -3
  19. package/dist/{chunk-APJO2XV5.js.map → chunk-HL33CGJF.js.map} +1 -1
  20. package/dist/{chunk-AUCXKRRH.js → chunk-RMPPYKUV.js} +167 -75
  21. package/dist/chunk-RMPPYKUV.js.map +1 -0
  22. package/dist/{chunk-BL7BBSYF.js → chunk-RUGJE5EB.js} +2 -2
  23. package/dist/{chunk-J4T5OSCF.js → chunk-YGRZU7IR.js} +322 -11
  24. package/dist/chunk-YGRZU7IR.js.map +1 -0
  25. package/dist/index.cjs +166 -74
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/{server-Z4MEISH5.js → server-JNY4XPZE.js} +138 -81
  29. package/dist/server-JNY4XPZE.js.map +1 -0
  30. package/dist/{site-extract-repository-U476J44K.js → site-extract-repository-NVSZH35Y.js} +3 -3
  31. package/dist/{worker-KJZ3ZN2N.js → worker-JQTS437L.js} +4 -4
  32. package/dist/worker-JQTS437L.js.map +1 -0
  33. package/package.json +2 -2
  34. package/dist/chunk-3RTPEQJC.js +0 -7
  35. package/dist/chunk-3RTPEQJC.js.map +0 -1
  36. package/dist/chunk-AUCXKRRH.js.map +0 -1
  37. package/dist/chunk-J4T5OSCF.js.map +0 -1
  38. package/dist/server-Z4MEISH5.js.map +0 -1
  39. package/dist/worker-KJZ3ZN2N.js.map +0 -1
  40. package/docs/spec-browser-routing.md +0 -108
  41. package/docs/spec-kernel-computer-controls.md +0 -166
  42. package/docs/spec-mcp-tools.md +0 -146
  43. package/docs/spec-server-instructions.md +0 -94
  44. package/docs/specs/agent-ready-seo-packet-spec.md +0 -237
  45. package/docs/specs/audit-visual-demo.md +0 -309
  46. package/docs/specs/cli-agent-wiring-spec.md +0 -206
  47. package/docs/specs/local-competitive-audit-spec.md +0 -311
  48. package/docs/specs/scheduled-workflows-api-spec.md +0 -304
  49. package/docs/specs/seo-cli-growth-roadmap-spec.md +0 -181
  50. package/docs/specs/seo-workflow-runner-and-reports-spec.md +0 -241
  51. /package/dist/{chunk-BL7BBSYF.js.map → chunk-RUGJE5EB.js.map} +0 -0
  52. /package/dist/{site-extract-repository-U476J44K.js.map → site-extract-repository-NVSZH35Y.js.map} +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  registerMemoryMcpTools,
8
8
  registerPaaExtractorMcpTools,
9
9
  registerSerpIntelligenceCaptureTools
10
- } from "../chunk-J4T5OSCF.js";
10
+ } from "../chunk-YGRZU7IR.js";
11
11
  import "../chunk-GWRIO6JT.js";
12
12
  import "../chunk-HPV4VOQX.js";
13
13
  import {
@@ -16,8 +16,8 @@ import {
16
16
  import "../chunk-XGIPATLV.js";
17
17
  import {
18
18
  PACKAGE_VERSION
19
- } from "../chunk-3RTPEQJC.js";
20
- import "../chunk-APJO2XV5.js";
19
+ } from "../chunk-EGWJ74EX.js";
20
+ import "../chunk-HL33CGJF.js";
21
21
  import "../chunk-M2S27J6Z.js";
22
22
 
23
23
  // bin/mcp-stdio-server.ts
@@ -1637,13 +1637,41 @@ var PAAExtractor = class {
1637
1637
  }
1638
1638
  return parts.join(" ").replace(/\s+/g, " ").trim();
1639
1639
  }
1640
- return Array.from(document.querySelectorAll(selectors.item)).map((pair) => ({
1641
- question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
1642
- answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
1643
- sourceTitle: pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0,
1644
- sourceSite: pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0,
1645
- sourceCite: pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0
1646
- }));
1640
+ return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {
1641
+ let sourceTitle = pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0;
1642
+ let sourceSite = pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0;
1643
+ let sourceCite = pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0;
1644
+ if (!sourceCite) {
1645
+ const seenHrefs = /* @__PURE__ */ new Set();
1646
+ const anchors = [];
1647
+ for (const a of Array.from(pair.querySelectorAll("a[href]"))) {
1648
+ const href = a.href;
1649
+ if (!/^https?:\/\//.test(href) || seenHrefs.has(href)) continue;
1650
+ let host = "";
1651
+ try {
1652
+ host = new URL(href).hostname;
1653
+ } catch {
1654
+ continue;
1655
+ }
1656
+ if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
1657
+ seenHrefs.add(href);
1658
+ anchors.push({ text: (a.textContent || "").trim(), href, host });
1659
+ }
1660
+ const primary = anchors.find((x) => x.text.length > 2) ?? anchors[0];
1661
+ if (primary) {
1662
+ sourceTitle = sourceTitle || primary.text || void 0;
1663
+ sourceSite = sourceSite || primary.host.replace(/^www\./, "");
1664
+ sourceCite = primary.href;
1665
+ }
1666
+ }
1667
+ return {
1668
+ question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
1669
+ answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
1670
+ sourceTitle,
1671
+ sourceSite,
1672
+ sourceCite
1673
+ };
1674
+ });
1647
1675
  }, sels);
1648
1676
  return raw.flatMap((item) => {
1649
1677
  const cleaned = {
@@ -1685,54 +1713,115 @@ var PAAExtractor = class {
1685
1713
  const seenQs = /* @__PURE__ */ new Set();
1686
1714
  const orderedQs = [];
1687
1715
  const results = [];
1688
- const readAllQs = () => page.evaluate(
1689
- ({ sel, dataQ, dataInitQ, questionEl }) => Array.from(document.querySelectorAll(sel)).map(
1690
- (el) => el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || ""
1691
- ).filter(Boolean),
1692
- { sel: PAASelectors.item, dataQ: PAASelectors.itemDataQ, dataInitQ: PAASelectors.itemDataInitQ, questionEl: PAASelectors.itemQuestionEl }
1716
+ const abandonedNeverReclick = /* @__PURE__ */ new Set();
1717
+ const clickedOnceEver = /* @__PURE__ */ new Set();
1718
+ const itemArgs = {
1719
+ sel: PAASelectors.item,
1720
+ dataQ: PAASelectors.itemDataQ,
1721
+ dataInitQ: PAASelectors.itemDataInitQ,
1722
+ questionEl: PAASelectors.itemQuestionEl,
1723
+ answerSel: PAASelectors.answerContainer,
1724
+ expandedClass: PAASelectors.expandedClass,
1725
+ clickTarget: PAASelectors.clickTarget
1726
+ };
1727
+ const readItemStates = () => page.evaluate(
1728
+ ({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }) => {
1729
+ const answerText = (root) => {
1730
+ if (!root) return "";
1731
+ let out = "";
1732
+ const walk = (n) => {
1733
+ for (const c of Array.from(n.childNodes)) {
1734
+ if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
1735
+ else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
1736
+ }
1737
+ };
1738
+ walk(root);
1739
+ return out.replace(/\s+/g, " ").trim();
1740
+ };
1741
+ return Array.from(document.querySelectorAll(sel)).map((el) => ({
1742
+ q: el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || "",
1743
+ done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30
1744
+ })).filter((s) => s.q);
1745
+ },
1746
+ itemArgs
1693
1747
  );
1748
+ const expandOneItemSerially = async (q) => {
1749
+ const clicked = await page.evaluate(
1750
+ ({ sel, dataQ, dataInitQ, clickTarget, q: target }) => {
1751
+ const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
1752
+ const btn = item?.querySelector(clickTarget);
1753
+ if (!btn) return false;
1754
+ btn.scrollIntoView({ block: "center" });
1755
+ btn.click();
1756
+ return true;
1757
+ },
1758
+ { ...itemArgs, q }
1759
+ ).catch(() => false);
1760
+ if (!clicked) return "failed";
1761
+ try {
1762
+ const outcome = await page.waitForFunction(
1763
+ ({ sel, dataQ, dataInitQ, answerSel, q: target }) => {
1764
+ const answerText = (root) => {
1765
+ if (!root) return "";
1766
+ let out = "";
1767
+ const walk = (n) => {
1768
+ for (const c of Array.from(n.childNodes)) {
1769
+ if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
1770
+ else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
1771
+ }
1772
+ };
1773
+ walk(root);
1774
+ return out.replace(/\s+/g, " ").trim();
1775
+ };
1776
+ const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
1777
+ if (!item) return "gone";
1778
+ const text = answerText(item.querySelector(answerSel));
1779
+ if (/an error has occurred/i.test(text.slice(0, 80))) return "error";
1780
+ if (text.length > 30) return "ok";
1781
+ return false;
1782
+ },
1783
+ { ...itemArgs, q },
1784
+ { timeout: 6500, polling: 200 }
1785
+ );
1786
+ return await outcome.jsonValue() === "ok" ? "ok" : "failed";
1787
+ } catch {
1788
+ return "failed";
1789
+ }
1790
+ };
1694
1791
  let round = 0;
1695
- while (seenQs.size < options.maxQuestions) {
1792
+ let growthWaits = 0;
1793
+ while (true) {
1696
1794
  if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
1697
1795
  this.throwIfAborted(signal);
1698
- await this.throwIfCaptcha(page, "Google PAA expansion");
1699
- const beforeQs = await readAllQs();
1700
- if (beforeQs.length >= options.maxQuestions) break;
1701
- const unexpandedSel = `${PAASelectors.item}:not(.${PAASelectors.expandedClass}) ${PAASelectors.clickTarget}`;
1702
- const unexpandedCount = await page.locator(unexpandedSel).count();
1703
- if (unexpandedCount === 0) break;
1704
- this.reporter.onDepth(++round);
1705
- for (let ci = 0; ci < unexpandedCount; ci++) {
1706
- if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
1707
- this.throwIfAborted(signal);
1708
- try {
1709
- const btn = page.locator(unexpandedSel).first();
1710
- await btn.scrollIntoViewIfNeeded();
1711
- await btn.hover({ force: true });
1712
- await page.waitForTimeout(100);
1713
- await btn.click({ force: true });
1714
- await page.waitForTimeout(500);
1715
- } catch {
1796
+ const states = await readItemStates();
1797
+ for (const s of states) {
1798
+ if (!seenQs.has(s.q)) {
1799
+ seenQs.add(s.q);
1800
+ orderedQs.push(s.q);
1716
1801
  }
1717
1802
  }
1718
- await page.waitForFunction(
1719
- ({ sel, min }) => document.querySelectorAll(sel).length > min,
1720
- { sel: PAASelectors.item, min: beforeQs.length },
1721
- { timeout: 5e3 }
1722
- ).catch(() => {
1723
- });
1724
- await this.throwIfCaptcha(page, "Google PAA expansion");
1725
- const afterQs = await readAllQs();
1726
- if (afterQs.length === beforeQs.length) break;
1727
- for (const q of afterQs) {
1728
- if (!seenQs.has(q)) {
1729
- seenQs.add(q);
1730
- orderedQs.push(q);
1731
- }
1803
+ const kept = new Set(orderedQs.slice(0, options.maxQuestions));
1804
+ const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
1805
+ const target = clickable.find((s) => !s.done) ?? (seenQs.size < options.maxQuestions ? clickable[0] : void 0);
1806
+ if (!target) {
1807
+ if (seenQs.size >= options.maxQuestions) break;
1808
+ if (++growthWaits > 1) break;
1809
+ const grew = await page.waitForFunction(
1810
+ ({ sel, min }) => document.querySelectorAll(sel).length > min,
1811
+ { sel: PAASelectors.item, min: states.length },
1812
+ { timeout: 3e3 }
1813
+ ).catch(() => null);
1814
+ if (!grew) break;
1815
+ continue;
1732
1816
  }
1817
+ growthWaits = 0;
1818
+ this.reporter.onDepth(++round);
1819
+ await this.throwIfCaptcha(page, "Google PAA expansion");
1820
+ clickedOnceEver.add(target.q);
1821
+ if (await expandOneItemSerially(target.q) === "failed") abandonedNeverReclick.add(target.q);
1733
1822
  }
1734
1823
  const itemMap = new Map((await this.extractVisibleItems(page)).map((i) => [i.question, i]));
1735
- await this.fillIncompleteItems(page, orderedQs, itemMap, options);
1824
+ await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
1736
1825
  for (const q of orderedQs) {
1737
1826
  if (results.length >= options.maxQuestions) break;
1738
1827
  const key = this.normalizeQuestion(q);
@@ -1750,9 +1839,11 @@ var PAAExtractor = class {
1750
1839
  this.completeness.paaWithoutSource = results.filter((r) => !r.source_title && !r.source_site && !r.source_cite).length;
1751
1840
  return results;
1752
1841
  }
1753
- async fillIncompleteItems(page, orderedQs, itemMap, options) {
1842
+ async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
1754
1843
  const kept = new Set(orderedQs.slice(0, options.maxQuestions));
1755
- const missingBefore = new Set([...kept].filter((q) => !itemMap.get(q)?.answer));
1844
+ const missingBefore = new Set(
1845
+ [...kept].filter((q) => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q))
1846
+ );
1756
1847
  if (missingBefore.size === 0) return;
1757
1848
  for (let round = 0; round < 2; round++) {
1758
1849
  if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
@@ -2144,40 +2235,41 @@ var PAAExtractor = class {
2144
2235
  };
2145
2236
  }
2146
2237
  async captureAIOverviewShareUrl(page, options) {
2147
- for (let attempt = 0; attempt < 3; attempt++) {
2148
- if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
2149
- const clicked = await page.evaluate(() => {
2150
- const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
2151
- for (const head of heads) {
2152
- let sec = head;
2153
- for (let up = 0; up < 8 && sec; up++) {
2154
- const btn = sec.querySelector('[aria-label="Share"]');
2155
- if (btn) {
2156
- btn.scrollIntoView({ block: "center" });
2157
- btn.click();
2158
- return true;
2159
- }
2160
- sec = sec.parentElement;
2238
+ if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
2239
+ const clicked = await page.evaluate(() => {
2240
+ const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
2241
+ for (const head of heads) {
2242
+ let sec = head;
2243
+ for (let up = 0; up < 8 && sec; up++) {
2244
+ const btn = sec.querySelector('[aria-label="Share"]');
2245
+ if (btn) {
2246
+ btn.scrollIntoView({ block: "center" });
2247
+ btn.click();
2248
+ return true;
2161
2249
  }
2250
+ sec = sec.parentElement;
2162
2251
  }
2163
- return false;
2164
- }).catch(() => false);
2165
- if (!clicked) return null;
2166
- await page.waitForTimeout(4500);
2167
- const url = await page.evaluate(() => {
2252
+ }
2253
+ return false;
2254
+ }).catch(() => false);
2255
+ if (!clicked) return null;
2256
+ let url = null;
2257
+ try {
2258
+ const found = await page.waitForFunction(() => {
2168
2259
  const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
2169
2260
  while (w.nextNode()) {
2170
2261
  const m = (w.currentNode.textContent || "").match(/https:\/\/share\.google\/aimode\/\S+/);
2171
2262
  if (m) return m[0];
2172
2263
  }
2173
- return null;
2174
- }).catch(() => null);
2175
- await page.keyboard.press("Escape").catch(() => {
2176
- });
2177
- if (url) return url;
2178
- await page.waitForTimeout(600);
2264
+ return false;
2265
+ }, void 0, { timeout: 4500, polling: 250 });
2266
+ url = await found.jsonValue();
2267
+ } catch {
2268
+ url = null;
2179
2269
  }
2180
- return null;
2270
+ await page.keyboard.press("Escape").catch(() => {
2271
+ });
2272
+ return url;
2181
2273
  }
2182
2274
  buildTree(flat, _seed) {
2183
2275
  const roots = [];