mcp-scraper 0.38.0 → 0.38.1

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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +98 -65
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +1 -1
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +4 -4
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +3 -3
  16. package/dist/{chunk-Q35WZJJK.js → chunk-4YAFJWFG.js} +2 -2
  17. package/dist/{chunk-LOPKN3YL.js → chunk-BSD2Z5A6.js} +2 -2
  18. package/dist/{chunk-MA5JBAUZ.js → chunk-C5UGV7RM.js} +2 -2
  19. package/dist/chunk-FFBPWVWC.js +7 -0
  20. package/dist/chunk-FFBPWVWC.js.map +1 -0
  21. package/dist/{chunk-FSAXLDB3.js → chunk-OLRRIA4H.js} +2 -2
  22. package/dist/{chunk-PUJFYJXB.js → chunk-WS2E7HA5.js} +2 -2
  23. package/dist/{chunk-CSCD2HNS.js → chunk-XKCZOPZN.js} +79 -63
  24. package/dist/chunk-XKCZOPZN.js.map +1 -0
  25. package/dist/{chunk-P64PZUCA.js → chunk-YAW3AUTP.js} +3 -3
  26. package/dist/{db-N3YECFWR.js → db-JRFMBTRD.js} +4 -2
  27. package/dist/{extract-bundle-346R6MXD.js → extract-bundle-DSFOHMAT.js} +3 -3
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/{location-data-repository-O2VII3ON.js → location-data-repository-OBJYZEGE.js} +3 -3
  31. package/dist/{server-3Q6XIJPF.js → server-2UDB6PTC.js} +24 -17
  32. package/dist/server-2UDB6PTC.js.map +1 -0
  33. package/dist/{site-extract-repository-I3VM6WXN.js → site-extract-repository-2YJ33J5C.js} +3 -3
  34. package/dist/{worker-TTFXPPDK.js → worker-ESF3HCNJ.js} +5 -5
  35. package/docs/mcp-tool-manifest.generated.json +2 -2
  36. package/package.json +4 -2
  37. package/dist/chunk-CSCD2HNS.js.map +0 -1
  38. package/dist/chunk-H3M3O457.js +0 -7
  39. package/dist/chunk-H3M3O457.js.map +0 -1
  40. package/dist/server-3Q6XIJPF.js.map +0 -1
  41. /package/dist/{chunk-Q35WZJJK.js.map → chunk-4YAFJWFG.js.map} +0 -0
  42. /package/dist/{chunk-LOPKN3YL.js.map → chunk-BSD2Z5A6.js.map} +0 -0
  43. /package/dist/{chunk-MA5JBAUZ.js.map → chunk-C5UGV7RM.js.map} +0 -0
  44. /package/dist/{chunk-FSAXLDB3.js.map → chunk-OLRRIA4H.js.map} +0 -0
  45. /package/dist/{chunk-PUJFYJXB.js.map → chunk-WS2E7HA5.js.map} +0 -0
  46. /package/dist/{chunk-P64PZUCA.js.map → chunk-YAW3AUTP.js.map} +0 -0
  47. /package/dist/{db-N3YECFWR.js.map → db-JRFMBTRD.js.map} +0 -0
  48. /package/dist/{extract-bundle-346R6MXD.js.map → extract-bundle-DSFOHMAT.js.map} +0 -0
  49. /package/dist/{location-data-repository-O2VII3ON.js.map → location-data-repository-OBJYZEGE.js.map} +0 -0
  50. /package/dist/{site-extract-repository-I3VM6WXN.js.map → site-extract-repository-2YJ33J5C.js.map} +0 -0
  51. /package/dist/{worker-TTFXPPDK.js.map → worker-ESF3HCNJ.js.map} +0 -0
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.38.0`, SHA-256 `2159a7e13422057e23aa3119246d7c8a19d6644751167b38272ba5c40e080be3`). 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.38.1`, SHA-256 `7b368db1bb9facbf2f7e1ec7d52128b8c49c682a02405a373ae613eba618aeae`). 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
 
@@ -3598,10 +3598,20 @@ var init_url_utils = __esm({
3598
3598
  }
3599
3599
  });
3600
3600
 
3601
+ // src/api/credit-lot-integrity.ts
3602
+ var CREDIT_LOTS_EPOCH;
3603
+ var init_credit_lot_integrity = __esm({
3604
+ "src/api/credit-lot-integrity.ts"() {
3605
+ "use strict";
3606
+ CREDIT_LOTS_EPOCH = "2026-06-26 21:58:03";
3607
+ }
3608
+ });
3609
+
3601
3610
  // src/api/db.ts
3602
3611
  var db_exports = {};
3603
3612
  __export(db_exports, {
3604
3613
  CORE_SCHEMA_VERSION: () => CORE_SCHEMA_VERSION,
3614
+ CREDIT_LOTS_EPOCH: () => CREDIT_LOTS_EPOCH,
3605
3615
  CREDIT_LOT_TTL: () => CREDIT_LOT_TTL,
3606
3616
  SiteAuditJobRowSchema: () => SiteAuditJobRowSchema,
3607
3617
  SiteAuditPhaseLogRowSchema: () => SiteAuditPhaseLogRowSchema,
@@ -3998,6 +4008,21 @@ async function migrate() {
3998
4008
  } catch {
3999
4009
  }
4000
4010
  await db.execute(`CREATE INDEX IF NOT EXISTS credit_lots_user_active ON credit_lots(user_id, expires_at, remaining_mc)`);
4011
+ await db.execute(`
4012
+ CREATE TABLE IF NOT EXISTS credit_lot_repairs (
4013
+ repair_id TEXT PRIMARY KEY,
4014
+ user_id INTEGER NOT NULL REFERENCES users(id),
4015
+ report_sha256 TEXT NOT NULL,
4016
+ classification TEXT NOT NULL,
4017
+ before_json TEXT NOT NULL,
4018
+ plan_json TEXT NOT NULL,
4019
+ after_json TEXT,
4020
+ status TEXT NOT NULL DEFAULT 'claimed',
4021
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
4022
+ applied_at TEXT
4023
+ )
4024
+ `);
4025
+ await db.execute(`CREATE INDEX IF NOT EXISTS credit_lot_repairs_user_applied ON credit_lot_repairs(user_id, applied_at)`);
4001
4026
  await db.execute(`
4002
4027
  CREATE TABLE IF NOT EXISTS free_credit_refreshes (
4003
4028
  user_id INTEGER NOT NULL REFERENCES users(id),
@@ -5530,32 +5555,25 @@ async function resolveUserByMemorySubscription(subscriptionId) {
5530
5555
  }
5531
5556
  async function ensureMigrated(userId) {
5532
5557
  const db = getDb();
5533
- const hasLots = await db.execute({ sql: "SELECT 1 FROM credit_lots WHERE user_id = ? LIMIT 1", args: [userId] });
5534
- if (hasLots.rows.length) return;
5535
- const legacy = await db.execute({ sql: "SELECT COALESCE(SUM(amount_mc), 0) AS bal FROM ledger WHERE user_id = ?", args: [userId] });
5536
- const bal = Number(legacy.rows[0]?.bal ?? 0);
5537
- if (bal > 0) {
5538
- await db.execute({
5539
- sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at) VALUES (?, ?, ?, 'migration', datetime('now', ?))`,
5540
- args: [userId, bal, bal, CREDIT_LOT_TTL]
5541
- });
5542
- }
5543
- }
5544
- async function drainLots(userId, mc) {
5545
- const db = getDb();
5546
- const lots = await db.execute({
5547
- sql: `SELECT id, remaining_mc FROM credit_lots WHERE user_id = ? AND remaining_mc > 0 AND (never_expires = 1 OR expires_at > datetime('now')) ORDER BY never_expires ASC, expires_at ASC, granted_at ASC, id ASC`,
5548
- args: [userId]
5558
+ const inserted = await db.execute({
5559
+ sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at)
5560
+ SELECT ?, legacy.balance_mc, legacy.balance_mc, 'migration', datetime('now', ?)
5561
+ FROM (
5562
+ SELECT COALESCE(SUM(amount_mc), 0) AS balance_mc
5563
+ FROM ledger
5564
+ WHERE user_id = ?
5565
+ ) AS legacy
5566
+ WHERE legacy.balance_mc > 0
5567
+ AND EXISTS (
5568
+ SELECT 1 FROM ledger
5569
+ WHERE user_id = ? AND created_at < ?
5570
+ )
5571
+ AND NOT EXISTS (
5572
+ SELECT 1 FROM credit_lots WHERE user_id = ?
5573
+ )`,
5574
+ args: [userId, CREDIT_LOT_TTL, userId, userId, CREDIT_LOTS_EPOCH, userId]
5549
5575
  });
5550
- let need = mc;
5551
- const updates = [];
5552
- for (const lot of lots.rows) {
5553
- if (need <= 0) break;
5554
- const take = Math.min(Number(lot.remaining_mc), need);
5555
- updates.push({ sql: "UPDATE credit_lots SET remaining_mc = remaining_mc - ? WHERE id = ?", args: [take, Number(lot.id)] });
5556
- need -= take;
5557
- }
5558
- if (updates.length) await db.batch(updates, "write");
5576
+ return inserted.rowsAffected;
5559
5577
  }
5560
5578
  async function expireOldLots() {
5561
5579
  const db = getDb();
@@ -5581,23 +5599,29 @@ async function expireOldLots() {
5581
5599
  async function migrateExistingBalancesToLots() {
5582
5600
  const db = getDb();
5583
5601
  const res = await db.execute({
5584
- sql: `SELECT u.id AS user_id, COALESCE(SUM(l.amount_mc), 0) AS bal
5585
- FROM users u LEFT JOIN ledger l ON l.user_id = u.id
5586
- WHERE u.id NOT IN (SELECT DISTINCT user_id FROM credit_lots)
5587
- GROUP BY u.id HAVING bal > 0`
5602
+ sql: `SELECT DISTINCT u.id AS user_id
5603
+ FROM users u
5604
+ JOIN ledger l ON l.user_id = u.id
5605
+ WHERE l.created_at < ?
5606
+ AND NOT EXISTS (SELECT 1 FROM credit_lots WHERE user_id = u.id)`,
5607
+ args: [CREDIT_LOTS_EPOCH]
5588
5608
  });
5589
5609
  let migrated = 0;
5590
5610
  let totalMc = 0;
5591
5611
  for (const row of res.rows) {
5592
5612
  const uid = Number(row.user_id);
5593
- const bal = Number(row.bal);
5594
- await db.execute({
5595
- sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at) VALUES (?, ?, ?, 'migration', datetime('now', ?))`,
5596
- args: [uid, bal, bal, CREDIT_LOT_TTL]
5597
- });
5613
+ const inserted = await ensureMigrated(uid);
5614
+ if (inserted === 0) continue;
5615
+ const lot = await db.execute({
5616
+ sql: `SELECT amount_mc FROM credit_lots
5617
+ WHERE user_id = ? AND source = 'migration'
5618
+ ORDER BY id DESC LIMIT 1`,
5619
+ args: [uid]
5620
+ });
5621
+ const amountMc = Number(lot.rows[0]?.amount_mc ?? 0);
5598
5622
  await reconcileBalanceMc(uid);
5599
- migrated++;
5600
- totalMc += bal;
5623
+ migrated += 1;
5624
+ totalMc += amountMc;
5601
5625
  }
5602
5626
  return { migrated, total_mc: totalMc };
5603
5627
  }
@@ -5611,19 +5635,22 @@ async function creditMc(userId, mc, operation, description, stripePaymentIntent,
5611
5635
  }
5612
5636
  const expiry = neverExpires ? "9999-12-31 23:59:59" : null;
5613
5637
  if (stripePaymentIntent) {
5614
- const ledgerInsert = await db.execute({
5638
+ const stmts2 = [{
5615
5639
  sql: "INSERT OR IGNORE INTO ledger (user_id, amount_mc, operation, description, stripe_pi) VALUES (?, ?, ?, ?, ?)",
5616
5640
  args: [userId, mc, operation, description ?? null, stripePaymentIntent]
5617
- });
5618
- if (ledgerInsert.rowsAffected === 0) {
5619
- const res3 = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
5620
- return Number(res3.rows[0]?.balance_mc ?? 0);
5621
- }
5622
- const stmts2 = [];
5641
+ }];
5623
5642
  if (mc > 0) {
5624
- stmts2.push({ sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires) VALUES (?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?)`, args: [userId, mc, mc, operation, stripePaymentIntent, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0] });
5643
+ stmts2.push({
5644
+ sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)
5645
+ SELECT ?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?
5646
+ WHERE changes() = 1`,
5647
+ args: [userId, mc, mc, operation, stripePaymentIntent, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0]
5648
+ });
5625
5649
  }
5626
- stmts2.push({ sql: "UPDATE users SET balance_mc = balance_mc + ? WHERE id = ?", args: [mc, userId] });
5650
+ stmts2.push({
5651
+ sql: "UPDATE users SET balance_mc = balance_mc + ? WHERE id = ? AND changes() = 1",
5652
+ args: [mc, userId]
5653
+ });
5627
5654
  await db.batch(stmts2, "write");
5628
5655
  const res2 = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
5629
5656
  return Number(res2.rows[0]?.balance_mc ?? 0);
@@ -5731,23 +5758,14 @@ async function claimMonthlyFreeRefresh(userId, month) {
5731
5758
  return res.rowsAffected > 0;
5732
5759
  }
5733
5760
  async function debitMc(userId, mc, operation, description) {
5734
- const db = getDb();
5735
- await ensureMigrated(userId);
5736
- const upd = await db.execute({
5737
- sql: "UPDATE users SET balance_mc = balance_mc - ? WHERE id = ? AND balance_mc >= ?",
5738
- args: [mc, userId, mc]
5739
- });
5740
- if (upd.rowsAffected === 0) {
5741
- const res2 = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
5742
- return { ok: false, balance_mc: Number(res2.rows[0]?.balance_mc ?? 0) };
5743
- }
5744
- await drainLots(userId, mc);
5745
- await db.execute({
5746
- sql: "INSERT INTO ledger (user_id, amount_mc, operation, description) VALUES (?, ?, ?, ?)",
5747
- args: [userId, -mc, operation, description ?? null]
5748
- });
5749
- const res = await db.execute({ sql: "SELECT balance_mc FROM users WHERE id = ?", args: [userId] });
5750
- return { ok: true, balance_mc: Number(res.rows[0]?.balance_mc ?? 0) };
5761
+ const debit = await debitMcIdempotent(
5762
+ userId,
5763
+ mc,
5764
+ operation,
5765
+ description ?? "",
5766
+ `debit:${(0, import_node_crypto.randomUUID)()}`
5767
+ );
5768
+ return { ok: debit.ok, balance_mc: debit.balance_mc };
5751
5769
  }
5752
5770
  async function debitMcIdempotent(userId, mc, operation, description, idempotencyKey2) {
5753
5771
  if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error("idempotent debit amount must be a positive integer");
@@ -6258,11 +6276,13 @@ var init_db = __esm({
6258
6276
  import_http = require("@libsql/client/http");
6259
6277
  import_node_crypto = require("crypto");
6260
6278
  import_zod = require("zod");
6279
+ init_credit_lot_integrity();
6280
+ init_credit_lot_integrity();
6261
6281
  DB_URL = process.env.TURSO_DATABASE_URL ?? "file:./paa-api.db";
6262
6282
  DB_TOKEN = process.env.TURSO_AUTH_TOKEN;
6263
6283
  _db = null;
6264
6284
  _rateLimitSchemaReady = false;
6265
- CORE_SCHEMA_VERSION = "2026-07-28.1";
6285
+ CORE_SCHEMA_VERSION = "2026-07-28.2";
6266
6286
  CORE_SCHEMA_TABLES = [
6267
6287
  "admin_credit_adjustment_events",
6268
6288
  "admin_credit_adjustments",
@@ -6275,6 +6295,7 @@ var init_db = __esm({
6275
6295
  "connected_account_billing",
6276
6296
  "concurrency_locks",
6277
6297
  "credit_lots",
6298
+ "credit_lot_repairs",
6278
6299
  "free_credit_refreshes",
6279
6300
  "harvest_attempts",
6280
6301
  "inbox_messages",
@@ -6311,6 +6332,7 @@ var init_db = __esm({
6311
6332
  "concurrency_locks_operation",
6312
6333
  "concurrency_locks_user_status",
6313
6334
  "credit_lots_user_active",
6335
+ "credit_lot_repairs_user_applied",
6314
6336
  "harvest_attempts_job_id",
6315
6337
  "harvest_attempts_user_created_at",
6316
6338
  "inbox_messages_user_sent_at",
@@ -37478,7 +37500,7 @@ var PACKAGE_VERSION;
37478
37500
  var init_version = __esm({
37479
37501
  "src/version.ts"() {
37480
37502
  "use strict";
37481
- PACKAGE_VERSION = "0.38.0";
37503
+ PACKAGE_VERSION = "0.38.1";
37482
37504
  }
37483
37505
  });
37484
37506
 
@@ -48348,6 +48370,16 @@ var init_browser_agent_console = __esm({
48348
48370
  }
48349
48371
  });
48350
48372
 
48373
+ // src/api/stripe-credit-policy.ts
48374
+ function shouldGrantScraperSubscriptionCredits(invoiceHasTierLine, invoiceTotal) {
48375
+ return invoiceHasTierLine && (invoiceTotal ?? 0) > 0;
48376
+ }
48377
+ var init_stripe_credit_policy = __esm({
48378
+ "src/api/stripe-credit-policy.ts"() {
48379
+ "use strict";
48380
+ }
48381
+ });
48382
+
48351
48383
  // src/api/stripe-routes.ts
48352
48384
  async function cancelStandaloneMemorySub(memSubId, baseSubId) {
48353
48385
  if (!memSubId || memSubId === baseSubId) return;
@@ -48384,6 +48416,7 @@ var init_stripe_routes = __esm({
48384
48416
  init_rates();
48385
48417
  init_memory();
48386
48418
  init_connected_account_billing();
48419
+ init_stripe_credit_policy();
48387
48420
  stripe = new import_stripe.default(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
48388
48421
  stripeApp = new import_hono22.Hono();
48389
48422
  stripeApp.post("/webhooks", async (c) => {
@@ -48405,7 +48438,7 @@ var init_stripe_routes = __esm({
48405
48438
  const id = linePriceId(l);
48406
48439
  return id && id in SUBSCRIPTION_TIERS;
48407
48440
  });
48408
- if (invoiceHasTierLine) {
48441
+ if (shouldGrantScraperSubscriptionCredits(invoiceHasTierLine, invoice.total)) {
48409
48442
  const subId = invoice.subscription;
48410
48443
  const liveBasePlanPriceId = subId ? findBasePlanItem(await stripe.subscriptions.retrieve(subId))?.price?.id : void 0;
48411
48444
  const tierPriceId = liveBasePlanPriceId ?? invoice.lines.data.map(linePriceId).find((id) => id && id in SUBSCRIPTION_TIERS);