mcp-scraper 0.3.44 → 0.3.46

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 (38) hide show
  1. package/dist/bin/api-server.cjs +612 -359
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  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 +648 -380
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/bin/paa-harvest.cjs.map +1 -1
  14. package/dist/bin/paa-harvest.js +2 -2
  15. package/dist/{chunk-X53SOJSL.js → chunk-BRVX6RDN.js} +2 -2
  16. package/dist/{chunk-DVRPXPYH.js → chunk-FUVQR6GK.js} +14 -3
  17. package/dist/chunk-FUVQR6GK.js.map +1 -0
  18. package/dist/{chunk-GMTS35L6.js → chunk-RXNHY3TF.js} +2 -2
  19. package/dist/{chunk-NONBSIES.js → chunk-SS5YBZVI.js} +685 -412
  20. package/dist/chunk-SS5YBZVI.js.map +1 -0
  21. package/dist/chunk-WT6OT2T3.js +7 -0
  22. package/dist/chunk-WT6OT2T3.js.map +1 -0
  23. package/dist/{db-LYQENFPW.js → db-H3S3M6KK.js} +2 -2
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/{server-AN6QUH6C.js → server-MGE3GYJW.js} +38 -80
  27. package/dist/server-MGE3GYJW.js.map +1 -0
  28. package/dist/{worker-H4EUD2Q5.js → worker-4CVMSUZR.js} +4 -4
  29. package/package.json +1 -1
  30. package/dist/chunk-3Q2NSHSR.js +0 -7
  31. package/dist/chunk-3Q2NSHSR.js.map +0 -1
  32. package/dist/chunk-DVRPXPYH.js.map +0 -1
  33. package/dist/chunk-NONBSIES.js.map +0 -1
  34. package/dist/server-AN6QUH6C.js.map +0 -1
  35. /package/dist/{chunk-X53SOJSL.js.map → chunk-BRVX6RDN.js.map} +0 -0
  36. /package/dist/{chunk-GMTS35L6.js.map → chunk-RXNHY3TF.js.map} +0 -0
  37. /package/dist/{db-LYQENFPW.js.map → db-H3S3M6KK.js.map} +0 -0
  38. /package/dist/{worker-H4EUD2Q5.js.map → worker-4CVMSUZR.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  harvest
3
- } from "./chunk-GMTS35L6.js";
3
+ } from "./chunk-RXNHY3TF.js";
4
4
  import "./chunk-3PRO376E.js";
5
5
  import "./chunk-K443GQY5.js";
6
6
  import "./chunk-BP27CZ5Q.js";
7
- import "./chunk-DVRPXPYH.js";
7
+ import "./chunk-FUVQR6GK.js";
8
8
 
9
9
  // src/video/VideoGenerator.ts
10
10
  import { execSync as execSync2 } from "child_process";
@@ -7,7 +7,9 @@ import {
7
7
  computeIssues,
8
8
  configureReportSaving,
9
9
  exportFanout,
10
+ getBlobStore,
10
11
  harvestTimeoutBudget,
12
+ hashOwnerId,
11
13
  outputBaseDir,
12
14
  registerBrowserAgentMcpTools,
13
15
  registerSerpIntelligenceCaptureTools,
@@ -16,7 +18,7 @@ import {
16
18
  renderLinkReport,
17
19
  sanitizeAttempts,
18
20
  sanitizeHarvestResult
19
- } from "./chunk-NONBSIES.js";
21
+ } from "./chunk-SS5YBZVI.js";
20
22
  import {
21
23
  csvRecords,
22
24
  listWorkflowDefinitions,
@@ -29,7 +31,7 @@ import {
29
31
  workflowStepCount,
30
32
  workflowSupportsSteps
31
33
  } from "./chunk-NNEIXK5L.js";
32
- import "./chunk-3Q2NSHSR.js";
34
+ import "./chunk-WT6OT2T3.js";
33
35
  import {
34
36
  BROWSER_OPEN_MIN_BALANCE_MC,
35
37
  CONCURRENCY_PRICE_ID,
@@ -52,7 +54,7 @@ import {
52
54
  harvestProblemResponse,
53
55
  insufficientBalanceResponse,
54
56
  serializeHarvestProblem
55
- } from "./chunk-X53SOJSL.js";
57
+ } from "./chunk-BRVX6RDN.js";
56
58
  import {
57
59
  BrowserDriver,
58
60
  MapsSelectors,
@@ -65,7 +67,7 @@ import {
65
67
  recordVendorUsage,
66
68
  resolveKernelProxyId,
67
69
  runWithCostContext
68
- } from "./chunk-GMTS35L6.js";
70
+ } from "./chunk-RXNHY3TF.js";
69
71
  import {
70
72
  CaptchaError,
71
73
  RECAPTCHA_INSTRUCTIONS,
@@ -161,7 +163,7 @@ import {
161
163
  setSubscriptionTier,
162
164
  setUserMemoryCreds,
163
165
  verifyPassword
164
- } from "./chunk-DVRPXPYH.js";
166
+ } from "./chunk-FUVQR6GK.js";
165
167
 
166
168
  // src/blog/registry.ts
167
169
  var posts = [
@@ -7420,58 +7422,6 @@ var siteAuditFn = inngest.createFunction(
7420
7422
  }
7421
7423
  );
7422
7424
 
7423
- // src/api/blob-store.ts
7424
- import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
7425
- import { homedir as homedir2 } from "os";
7426
- import { join as join4, dirname as dirname2 } from "path";
7427
- function byteLength(data) {
7428
- return Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
7429
- }
7430
- var LocalBlobStore = class {
7431
- constructor(baseDir) {
7432
- this.baseDir = baseDir;
7433
- }
7434
- baseDir;
7435
- kind = "local";
7436
- async put(key, data, contentType = "application/octet-stream") {
7437
- const path5 = join4(this.baseDir, "blobs", key);
7438
- mkdirSync2(dirname2(path5), { recursive: true });
7439
- writeFileSync(path5, data);
7440
- return { key, url: `file://${path5}`, bytes: byteLength(data), contentType };
7441
- }
7442
- };
7443
- function localBaseDir() {
7444
- return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join4(homedir2(), "Downloads", "mcp-scraper");
7445
- }
7446
- var cached = null;
7447
- function getBlobStore() {
7448
- if (cached) return cached;
7449
- if (process.env.BLOB_READ_WRITE_TOKEN) {
7450
- cached = new VercelBlobStore(process.env.BLOB_READ_WRITE_TOKEN);
7451
- } else {
7452
- cached = new LocalBlobStore(localBaseDir());
7453
- }
7454
- return cached;
7455
- }
7456
- var VercelBlobStore = class {
7457
- constructor(token) {
7458
- this.token = token;
7459
- }
7460
- token;
7461
- kind = "vercel-blob";
7462
- async put(key, data, contentType = "application/octet-stream") {
7463
- const { put } = await import("@vercel/blob");
7464
- const body = Buffer.isBuffer(data) ? data : Buffer.from(data);
7465
- const result = await put(key, body, {
7466
- access: "public",
7467
- token: this.token,
7468
- contentType,
7469
- addRandomSuffix: true
7470
- });
7471
- return { key, url: result.url, bytes: byteLength(data), contentType };
7472
- }
7473
- };
7474
-
7475
7425
  // src/api/site-extract-repository.ts
7476
7426
  function rowToJob(r) {
7477
7427
  return {
@@ -11180,9 +11130,9 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
11180
11130
  // src/api/instagram-routes.ts
11181
11131
  import { Hono as Hono7 } from "hono";
11182
11132
  import { z as z15 } from "zod";
11183
- import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
11184
- import { homedir as homedir3 } from "os";
11185
- import { join as join5 } from "path";
11133
+ import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync2, statSync, writeFileSync } from "fs";
11134
+ import { homedir as homedir2 } from "os";
11135
+ import { join as join4 } from "path";
11186
11136
  import { Readable as Readable2 } from "stream";
11187
11137
  import { pipeline as pipeline2 } from "stream/promises";
11188
11138
  import { spawn } from "child_process";
@@ -11609,7 +11559,7 @@ async function resolveInstagramLaunch(body) {
11609
11559
  };
11610
11560
  }
11611
11561
  function outputBaseDir2() {
11612
- return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join5(homedir3(), "Downloads", "mcp-scraper");
11562
+ return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join4(homedir2(), "Downloads", "mcp-scraper");
11613
11563
  }
11614
11564
  function safeFilePart(input) {
11615
11565
  return input.replace(/^https?:\/\//, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "instagram";
@@ -11617,8 +11567,8 @@ function safeFilePart(input) {
11617
11567
  function mediaOutputDir(shortcode, sourceUrl) {
11618
11568
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
11619
11569
  const slug = shortcode ? `instagram-${shortcode}` : safeFilePart(sourceUrl);
11620
- const dir = join5(outputBaseDir2(), "instagram", `${stamp}-${slug}`);
11621
- mkdirSync3(dir, { recursive: true });
11570
+ const dir = join4(outputBaseDir2(), "instagram", `${stamp}-${slug}`);
11571
+ mkdirSync2(dir, { recursive: true });
11622
11572
  return dir;
11623
11573
  }
11624
11574
  async function downloadToFile(url, destPath, referer) {
@@ -11758,8 +11708,8 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11758
11708
  let outputDir = null;
11759
11709
  if (body.downloadMedia) {
11760
11710
  outputDir = mediaOutputDir(extraction.shortcode, sourceUrl.href);
11761
- const textPath = join5(outputDir, `${extraction.shortcode ?? "instagram"}-text.txt`);
11762
- writeFileSync2(textPath, [
11711
+ const textPath = join4(outputDir, `${extraction.shortcode ?? "instagram"}-text.txt`);
11712
+ writeFileSync(textPath, [
11763
11713
  `URL: ${extraction.pageUrl}`,
11764
11714
  extraction.caption ? `Caption: ${extraction.caption}` : "",
11765
11715
  "",
@@ -11767,7 +11717,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11767
11717
  ].filter(Boolean).join("\n"), "utf8");
11768
11718
  downloads.push({ kind: "text", url: null, savedPath: textPath, sizeBytes: statSync(textPath).size, mimeType: "text/plain", error: null });
11769
11719
  if (mediaTypes.has("image") && extraction.imageUrl) {
11770
- const imagePathBase = join5(outputDir, `${extraction.shortcode ?? "instagram"}-image`);
11720
+ const imagePathBase = join4(outputDir, `${extraction.shortcode ?? "instagram"}-image`);
11771
11721
  try {
11772
11722
  const downloaded = await downloadToFile(extraction.imageUrl, imagePathBase, extraction.pageUrl);
11773
11723
  const ext = extFromMime(downloaded.mimeType, "jpg");
@@ -11787,7 +11737,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11787
11737
  if (track.streamType === "video" && !mediaTypes.has("video")) continue;
11788
11738
  if (track.streamType === "audio" && !mediaTypes.has("audio")) continue;
11789
11739
  const kind = track.streamType === "audio" ? "audio" : "video";
11790
- const target = join5(outputDir, trackFilename(track, index, extraction.shortcode));
11740
+ const target = join4(outputDir, trackFilename(track, index, extraction.shortcode));
11791
11741
  try {
11792
11742
  const downloaded = await downloadToFile(track.url, target, extraction.pageUrl);
11793
11743
  downloads.push({ kind, url: track.url, savedPath: downloaded.savedPath, sizeBytes: downloaded.sizeBytes, mimeType: downloaded.mimeType, error: null });
@@ -11797,7 +11747,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11797
11747
  }
11798
11748
  }
11799
11749
  if (body.mux && mediaTypes.has("video") && mediaTypes.has("audio") && extraction.selectedVideoTrack && extraction.selectedAudioTrack && savedByUrl.has(extraction.selectedVideoTrack.url) && savedByUrl.has(extraction.selectedAudioTrack.url)) {
11800
- const outPath = join5(outputDir, `${extraction.shortcode ?? "instagram"}-muxed.mp4`);
11750
+ const outPath = join4(outputDir, `${extraction.shortcode ?? "instagram"}-muxed.mp4`);
11801
11751
  const muxed = await runFfmpegMux(savedByUrl.get(extraction.selectedVideoTrack.url), savedByUrl.get(extraction.selectedAudioTrack.url), outPath);
11802
11752
  if (muxed.ok) {
11803
11753
  downloads.push({ kind: "muxed_video", url: null, savedPath: outPath, sizeBytes: statSync(outPath).size, mimeType: "video/mp4", error: null });
@@ -13136,7 +13086,7 @@ import { Hono as Hono11 } from "hono";
13136
13086
 
13137
13087
  // src/directory/directory-workflow.ts
13138
13088
  import { mkdir as mkdir2, writeFile } from "fs/promises";
13139
- import { join as join6 } from "path";
13089
+ import { join as join5 } from "path";
13140
13090
  import { z as z19 } from "zod";
13141
13091
 
13142
13092
  // src/directory/location-db.ts
@@ -13472,11 +13422,11 @@ function csvRowsFor(result) {
13472
13422
  return rows;
13473
13423
  }
13474
13424
  async function saveDirectoryCsv(result) {
13475
- const outDir = join6(outputBaseDir(), "directory-workflows");
13425
+ const outDir = join5(outputBaseDir(), "directory-workflows");
13476
13426
  await mkdir2(outDir, { recursive: true });
13477
13427
  const stamp = result.extractedAt.replace(/[:.]/g, "-");
13478
13428
  const slug = `${result.state}-${result.query}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
13479
- const path5 = join6(outDir, `${stamp}-${slug}-directory-workflow.csv`);
13429
+ const path5 = join5(outDir, `${stamp}-${slug}-directory-workflow.csv`);
13480
13430
  const headers = [
13481
13431
  "source_query",
13482
13432
  "source_location",
@@ -15117,9 +15067,9 @@ mcpApp.all("/", async (c) => {
15117
15067
  enableJsonResponse: true
15118
15068
  });
15119
15069
  const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
15120
- const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
15070
+ const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
15121
15071
  registerSerpIntelligenceCaptureTools(server, executor);
15122
- registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
15072
+ registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
15123
15073
  await server.connect(transport);
15124
15074
  return transport.handleRequest(c.req.raw);
15125
15075
  } catch {
@@ -17387,6 +17337,7 @@ function isScraperResource(resource) {
17387
17337
  var ACCESS_TTL_SECONDS = 3600;
17388
17338
  var REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
17389
17339
  var CODE_TTL_SECONDS = 60;
17340
+ var ROTATION_GRACE_SECONDS = 120;
17390
17341
  var secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
17391
17342
  var sessionCookieOptions = {
17392
17343
  httpOnly: true,
@@ -17762,6 +17713,13 @@ oauthApp.post("/oauth/token", async (c) => {
17762
17713
  const refreshToken = get("refresh_token") ?? "";
17763
17714
  const clientId = get("client_id") ?? "";
17764
17715
  const record = await getRefresh(refreshToken);
17716
+ if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
17717
+ const graceUser = await getUserByEmail(record.identity);
17718
+ const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
17719
+ const graceAudience = record.resource ?? RESOURCE();
17720
+ const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
17721
+ return c.json({ access_token: graceAccess, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: record.replaced_by, scope: record.scope });
17722
+ }
17765
17723
  if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
17766
17724
  if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
17767
17725
  if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
@@ -18868,8 +18826,8 @@ async function persistScrapeBody(user, opts) {
18868
18826
 
18869
18827
  // src/api/scrape-blob-cleanup.ts
18870
18828
  import { readdir, stat, unlink } from "fs/promises";
18871
- import { homedir as homedir4 } from "os";
18872
- import { join as join7 } from "path";
18829
+ import { homedir as homedir3 } from "os";
18830
+ import { join as join6 } from "path";
18873
18831
  async function cleanupVercel(token, cutoff) {
18874
18832
  const { list, del } = await import("@vercel/blob");
18875
18833
  let cursor;
@@ -18886,8 +18844,8 @@ async function cleanupVercel(token, cutoff) {
18886
18844
  return { deleted, store: "vercel-blob" };
18887
18845
  }
18888
18846
  async function cleanupLocal(cutoff) {
18889
- const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join7(homedir4(), "Downloads", "mcp-scraper");
18890
- const dir = join7(baseDir, "blobs", SCRAPE_FALLBACK_PREFIX.replace(/\/$/, ""));
18847
+ const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join6(homedir3(), "Downloads", "mcp-scraper");
18848
+ const dir = join6(baseDir, "blobs", SCRAPE_FALLBACK_PREFIX.replace(/\/$/, ""));
18891
18849
  let deleted = 0;
18892
18850
  let entries;
18893
18851
  try {
@@ -18896,7 +18854,7 @@ async function cleanupLocal(cutoff) {
18896
18854
  return { deleted: 0, store: "local" };
18897
18855
  }
18898
18856
  for (const name of entries) {
18899
- const path5 = join7(dir, name);
18857
+ const path5 = join6(dir, name);
18900
18858
  try {
18901
18859
  const s = await stat(path5);
18902
18860
  if (s.isFile() && s.mtimeMs < cutoff) {
@@ -20564,7 +20522,7 @@ app.get("/cron/tick", async (c) => {
20564
20522
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
20565
20523
  return c.json({ error: "Unauthorized" }, 401);
20566
20524
  }
20567
- const { drainQueue } = await import("./worker-H4EUD2Q5.js");
20525
+ const { drainQueue } = await import("./worker-4CVMSUZR.js");
20568
20526
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
20569
20527
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
20570
20528
  const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
@@ -20717,4 +20675,4 @@ app.get("/blog/:slug/", (c) => {
20717
20675
  export {
20718
20676
  app
20719
20677
  };
20720
- //# sourceMappingURL=server-AN6QUH6C.js.map
20678
+ //# sourceMappingURL=server-MGE3GYJW.js.map