mcp-scraper 0.57.1 → 0.57.3

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to MCP Scraper are documented here. The format is based on [
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.57.3] - 2026-08-16
8
+
9
+ ### Fixed
10
+
11
+ - Local Sourcebook exact-place matching no longer rejects a Google listing that appends a service tagline to the business name. `nameOverlap` divided the intersection by the larger token set, so "Meljestic Spa" against "Meljestic Spa: Laser hair removal and Skincare" scored 0.33 against a 0.6 threshold and could never publish — even though the submitted name is fully contained in the Google name and Maps linked the same domain. Identity now also accepts containment of the smaller token set (which must carry two distinctive tokens) or a Maps listing that links the submitted domain. Wrong domain, wrong state, missing address, and single-generic-token still reject.
12
+ - `acquisition_error` is now returned as `acquisitionError` on the submission. It was written on every failure and never selected back, so no response explained why a listing failed.
13
+ - `local_sourcebook_submission_status` and `local_sourcebook_refresh` now state what a failed status means, that coverage counters are only written on success (so zeroed counters do not mean the crawl never ran), and that a replayed refresh still re-runs acquisition.
14
+
15
+ ## [0.57.2] - 2026-08-16
16
+
17
+ ### Fixed
18
+
19
+ - Blob storage no longer falls back to the local filesystem on a serverless deployment. `BLOB_READ_WRITE_TOKEN` was empty in production, so `getBlobStore()` returned a local store and every write through it — the scrape-vault fallback document and Instagram media downloads — was saved to an ephemeral path inside the function and handed back as a `file://` URL no request could read. Without a token a serverless deployment now returns a store that refuses to write and says why.
20
+
7
21
  ## [0.57.1] - 2026-08-16
8
22
 
9
23
  ### Fixed
package/README.md CHANGED
@@ -90,7 +90,7 @@ Build the branded one-click bundle:
90
90
  npm run build:mcpb
91
91
  ```
92
92
 
93
- The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.57.1`, SHA-256 `ea84195321a47e7550348be6f61ed3e6a293a679c20a6cd39e77cd37870bd636`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
93
+ The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.57.3`, SHA-256 `b1edddd21790e7e5f24bacad85d992d8e995199c5d7e1d314500ba0abaab117c`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
94
94
 
95
95
  The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
96
96
 
@@ -29050,6 +29050,7 @@ function mapSubmission(row) {
29050
29050
  businessName: String(rowValue(row, "business_name")),
29051
29051
  websiteUrl: String(rowValue(row, "website_url")),
29052
29052
  status: String(rowValue(row, "status")),
29053
+ acquisitionError: rowValue(row, "acquisition_error") == null ? null : String(rowValue(row, "acquisition_error")),
29053
29054
  draftRevision: Number(rowValue(row, "draft_revision")),
29054
29055
  publishedRevision: rowValue(row, "published_revision") == null ? null : Number(rowValue(row, "published_revision")),
29055
29056
  coverage: parseJson2(rowValue(row, "coverage_json")),
@@ -30915,6 +30916,15 @@ function nameOverlap(left, right) {
30915
30916
  const intersection = [...a].filter((token6) => b.has(token6)).length;
30916
30917
  return intersection / Math.max(a.size, b.size);
30917
30918
  }
30919
+ function nameContainment(left, right) {
30920
+ const a = tokens(left);
30921
+ const b = tokens(right);
30922
+ if (!a.size || !b.size) return 0;
30923
+ const smaller = a.size <= b.size ? a : b;
30924
+ if (smaller.size < 2) return 0;
30925
+ const intersection = [...a].filter((token6) => b.has(token6)).length;
30926
+ return intersection / smaller.size;
30927
+ }
30918
30928
  function validateLocalSourcebookMapsIdentity(submission, maps) {
30919
30929
  const expectedHost = host(submission.websiteUrl);
30920
30930
  const mapsHost = host(maps.website);
@@ -30924,7 +30934,9 @@ function validateLocalSourcebookMapsIdentity(submission, maps) {
30924
30934
  if (mapsHost && expectedHost && mapsHost !== expectedHost && !mapsHost.endsWith(`.${expectedHost}`) && !expectedHost.endsWith(`.${mapsHost}`)) {
30925
30935
  throw new Error(`Exact-place match rejected: Google Maps linked ${mapsHost}, not the submitted domain ${expectedHost}.`);
30926
30936
  }
30927
- if (overlap < 0.6) {
30937
+ const containment = nameContainment(submission.businessName, actualName);
30938
+ const domainConfirmsIdentity = Boolean(mapsHost && expectedHost);
30939
+ if (overlap < 0.6 && containment < 0.9 && !domainConfirmsIdentity) {
30928
30940
  throw new Error(`Exact-place match rejected: Google Maps returned \u201C${actualName},\u201D which does not sufficiently match \u201C${submission.businessName}.\u201D`);
30929
30941
  }
30930
30942
  if (addressState && addressState !== submission.state.toLowerCase()) {
@@ -37303,16 +37315,21 @@ function byteLength(data) {
37303
37315
  function localBaseDir2() {
37304
37316
  return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path11.join)((0, import_node_os7.homedir)(), "Downloads", "mcp-scraper");
37305
37317
  }
37318
+ function runsWithoutLocalDisk(env = process.env) {
37319
+ return Boolean(env.VERCEL || env.AWS_LAMBDA_FUNCTION_NAME);
37320
+ }
37321
+ function resolveBlobStore(env = process.env) {
37322
+ const token6 = env.BLOB_READ_WRITE_TOKEN?.trim();
37323
+ if (token6) return new VercelBlobStore(token6);
37324
+ if (runsWithoutLocalDisk(env)) return new UnconfiguredBlobStore();
37325
+ return new LocalBlobStore(localBaseDir2());
37326
+ }
37306
37327
  function getBlobStore() {
37307
37328
  if (cached) return cached;
37308
- if (process.env.BLOB_READ_WRITE_TOKEN) {
37309
- cached = new VercelBlobStore(process.env.BLOB_READ_WRITE_TOKEN);
37310
- } else {
37311
- cached = new LocalBlobStore(localBaseDir2());
37312
- }
37329
+ cached = resolveBlobStore();
37313
37330
  return cached;
37314
37331
  }
37315
- var import_node_fs7, import_node_os7, import_node_path11, LocalBlobStore, cached, VercelBlobStore;
37332
+ var import_node_fs7, import_node_os7, import_node_path11, LocalBlobStore, cached, UnconfiguredBlobStore, VercelBlobStore;
37316
37333
  var init_blob_store = __esm({
37317
37334
  "src/api/blob-store.ts"() {
37318
37335
  "use strict";
@@ -37342,6 +37359,15 @@ var init_blob_store = __esm({
37342
37359
  }
37343
37360
  };
37344
37361
  cached = null;
37362
+ UnconfiguredBlobStore = class {
37363
+ kind = "unconfigured";
37364
+ async put() {
37365
+ throw new Error("Blob storage is not configured on this deployment: set BLOB_READ_WRITE_TOKEN. Refusing to write to a local path that no request can read back.");
37366
+ }
37367
+ async get() {
37368
+ return null;
37369
+ }
37370
+ };
37345
37371
  VercelBlobStore = class {
37346
37372
  constructor(token6) {
37347
37373
  this.token = token6;
@@ -44624,7 +44650,7 @@ var PACKAGE_VERSION;
44624
44650
  var init_version = __esm({
44625
44651
  "src/version.ts"() {
44626
44652
  "use strict";
44627
- PACKAGE_VERSION = "0.57.1";
44653
+ PACKAGE_VERSION = "0.57.3";
44628
44654
  }
44629
44655
  });
44630
44656
 
@@ -50633,14 +50659,14 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
50633
50659
  }, async (input) => executor.localSourcebookCapture(input));
50634
50660
  server.registerTool("local_sourcebook_submission_status", {
50635
50661
  title: "Local Sourcebook Submission Status",
50636
- description: "Read the authenticated caller\u2019s listing draft, enrichment coverage, immutable revision number, publication state, and exact live LocalSourcebook.com profile and reviews URLs.",
50662
+ description: "Read the authenticated caller\u2019s listing draft, enrichment coverage, immutable revision number, publication state, and exact live LocalSourcebook.com profile and reviews URLs. When status is failed, acquisitionError states why in plain language \u2014 read it before retrying, because a refresh repeats the same acquisition and will fail the same way until the cause is fixed. Coverage counters are written only when acquisition succeeds, so a failed listing still shows the pre-run crawl block; zeroed counters do not mean the crawl never ran.",
50637
50663
  inputSchema: LocalSourcebookSubmissionStatusInputSchema,
50638
50664
  outputSchema: recordOutputSchema("local_sourcebook_submission_status", LocalSourcebookOutputSchema),
50639
50665
  annotations: localPlanningToolAnnotations("Local Sourcebook Submission Status")
50640
50666
  }, async (input) => executor.localSourcebookSubmissionStatus(input));
50641
50667
  server.registerTool("local_sourcebook_refresh", {
50642
50668
  title: "Refresh a Local Sourcebook Listing",
50643
- description: "Queue a new broad crawl and review/media acquisition pass for a listing owned by the authenticated MCP Scraper account. A refresh costs 2 Credits total, including acquisition; idempotent retries are not charged twice. The last published revision remains public until the refreshed evidence revision completes and auto-publishes.",
50669
+ description: "Queue a new broad crawl and review/media acquisition pass for a listing owned by the authenticated MCP Scraper account. A refresh costs 2 Credits total, including acquisition; idempotent retries are not charged twice, and a replayed retry still re-runs the acquisition, so a failed listing is always recoverable. Read local_sourcebook_submission_status first: a refresh repeats the same acquisition, so fix whatever acquisitionError reports before spending another pass. The last published revision remains public until the refreshed evidence revision completes and auto-publishes.",
50644
50670
  inputSchema: LocalSourcebookRefreshInputSchema,
50645
50671
  outputSchema: recordOutputSchema("local_sourcebook_refresh", LocalSourcebookOutputSchema),
50646
50672
  annotations: liveWebToolAnnotations("Refresh a Local Sourcebook Listing")