mcp-scraper 0.4.0 → 0.4.2

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 (48) hide show
  1. package/dist/bin/api-server.cjs +934 -221
  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 +2 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +3 -3
  7. package/dist/bin/mcp-scraper-install.cjs +3 -2
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +2 -2
  10. package/dist/bin/mcp-stdio-server.cjs +83 -22
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +4 -4
  13. package/dist/bin/paa-harvest.cjs +1 -0
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +2 -2
  16. package/dist/{chunk-6TDUY7CA.js → chunk-4MFYMUIF.js} +25 -3
  17. package/dist/chunk-4MFYMUIF.js.map +1 -0
  18. package/dist/{chunk-T3VZD2I4.js → chunk-5HNHYUJ5.js} +3 -2
  19. package/dist/chunk-5HNHYUJ5.js.map +1 -0
  20. package/dist/chunk-7HICT4GY.js +7 -0
  21. package/dist/chunk-7HICT4GY.js.map +1 -0
  22. package/dist/{chunk-NNEIXK5L.js → chunk-FUWZWKGO.js} +2 -2
  23. package/dist/{chunk-3NIPPJGP.js → chunk-GFY6XHZS.js} +80 -21
  24. package/dist/chunk-GFY6XHZS.js.map +1 -0
  25. package/dist/{chunk-HBC4C75S.js → chunk-NJK5BTUK.js} +2 -2
  26. package/dist/{chunk-JOEZJL4I.js → chunk-NVUBFCJK.js} +2 -2
  27. package/dist/{chunk-BP27CZ5Q.js → chunk-XGIPATLV.js} +2 -1
  28. package/dist/chunk-XGIPATLV.js.map +1 -0
  29. package/dist/index.cjs +1 -0
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/{server-Z4WKTE6F.js → server-6IYQQK6X.js} +793 -210
  33. package/dist/server-6IYQQK6X.js.map +1 -0
  34. package/dist/{site-extract-repository-2MQBAOX5.js → site-extract-repository-4NVO5KQA.js} +3 -3
  35. package/dist/{worker-RVZXIALX.js → worker-7XRZNLKB.js} +4 -4
  36. package/package.json +2 -1
  37. package/dist/chunk-3NIPPJGP.js.map +0 -1
  38. package/dist/chunk-6TDUY7CA.js.map +0 -1
  39. package/dist/chunk-BP27CZ5Q.js.map +0 -1
  40. package/dist/chunk-IXDYL7TE.js +0 -7
  41. package/dist/chunk-IXDYL7TE.js.map +0 -1
  42. package/dist/chunk-T3VZD2I4.js.map +0 -1
  43. package/dist/server-Z4WKTE6F.js.map +0 -1
  44. /package/dist/{chunk-NNEIXK5L.js.map → chunk-FUWZWKGO.js.map} +0 -0
  45. /package/dist/{chunk-HBC4C75S.js.map → chunk-NJK5BTUK.js.map} +0 -0
  46. /package/dist/{chunk-JOEZJL4I.js.map → chunk-NVUBFCJK.js.map} +0 -0
  47. /package/dist/{site-extract-repository-2MQBAOX5.js.map → site-extract-repository-4NVO5KQA.js.map} +0 -0
  48. /package/dist/{worker-RVZXIALX.js.map → worker-7XRZNLKB.js.map} +0 -0
@@ -10042,7 +10042,9 @@ var init_rates = __esm({
10042
10042
  instagram_transcribe: 50,
10043
10043
  browser_minute: 12e3,
10044
10044
  reddit_thread: 2e3,
10045
- video_analysis: 666667
10045
+ video_analysis: 666667,
10046
+ trustpilot_reviews: 500,
10047
+ trustpilot_review: 100
10046
10048
  };
10047
10049
  MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 6e4;
10048
10050
  BROWSER_OPEN_MIN_BALANCE_MC = 1e3;
@@ -10213,6 +10215,22 @@ var init_rates = __esm({
10213
10215
  credits: mcToCredits(MC_COSTS.video_analysis),
10214
10216
  unit: "per 120 frames (max 480)",
10215
10217
  notes: "Full multi-lens video breakdown: samples frames across a video (up to 30 minutes), analyzes each with vision AI, transcribes the audio, then produces summary, pacing/energy, words-per-minute, topic outline, key points, hook analysis, visual style, and a how-to-replicate recipe with a quality-control pass. $1 per 120 frames requested (max 480 = $4); billed down automatically if the video cannot use the requested frames, and refunded fully if the run fails."
10218
+ },
10219
+ {
10220
+ key: "trustpilot_reviews",
10221
+ label: "Trustpilot review harvest",
10222
+ aliases: ["trustpilot_reviews", "trustpilot", "trustpilot reviews"],
10223
+ credits: mcToCredits(MC_COSTS.trustpilot_reviews),
10224
+ unit: "per call",
10225
+ notes: "Base lookup. Reviews billed separately per card at trustpilot_review rate. Refunded if no reviews are found."
10226
+ },
10227
+ {
10228
+ key: "trustpilot_review",
10229
+ label: "Trustpilot review card",
10230
+ aliases: ["trustpilot_review", "trustpilot card"],
10231
+ credits: mcToCredits(MC_COSTS.trustpilot_review),
10232
+ unit: "per review card",
10233
+ notes: "Charged per review actually extracted, billed down automatically if the balance runs out mid-page."
10216
10234
  }
10217
10235
  ];
10218
10236
  CONCURRENCY_PRICE_ID = "price_1Ta1NRS8aAcsk3TGwsRnYbix";
@@ -10290,7 +10308,11 @@ var init_rates = __esm({
10290
10308
  VIDEO_ANALYSIS_REFUND: "video_analysis_refund",
10291
10309
  VIDEO_ANALYSIS_ADJUST: "video_analysis_adjust",
10292
10310
  MEMORY_AI: "memory_ai",
10293
- MEMORY_AI_REFUND: "memory_ai_refund"
10311
+ MEMORY_AI_REFUND: "memory_ai_refund",
10312
+ TRUSTPILOT_REVIEWS: "trustpilot_reviews",
10313
+ TRUSTPILOT_REVIEWS_REFUND: "trustpilot_reviews_refund",
10314
+ TRUSTPILOT_REVIEW: "trustpilot_review",
10315
+ TRUSTPILOT_REVIEW_REFUND: "trustpilot_review_refund"
10294
10316
  };
10295
10317
  MEMORY_AI_MARGIN_MULTIPLE = 3;
10296
10318
  MC_PER_USD = SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].credits_mc / SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].monthly_usd;
@@ -15063,6 +15085,7 @@ var init_schemas3 = __esm({
15063
15085
  hl: import_zod15.z.string().length(2).default("en"),
15064
15086
  includeReviews: import_zod15.z.boolean().default(false),
15065
15087
  maxReviews: import_zod15.z.number().int().min(1).max(500).default(50),
15088
+ includeServices: import_zod15.z.boolean().default(false),
15066
15089
  kernelApiKey: import_zod15.z.string().optional(),
15067
15090
  kernelProxyId: import_zod15.z.string().optional(),
15068
15091
  headless: import_zod15.z.boolean().default(true)
@@ -16889,6 +16912,23 @@ async function provisionMemoryForUser(user) {
16889
16912
  } catch {
16890
16913
  }
16891
16914
  }
16915
+ function memoryPlanForUser(user) {
16916
+ switch (user.subscription_tier) {
16917
+ case "starter":
16918
+ return "pro";
16919
+ case "growth":
16920
+ return "team";
16921
+ case "scale":
16922
+ return "enterprise";
16923
+ default:
16924
+ return "free";
16925
+ }
16926
+ }
16927
+ function resolveEffectiveMemoryPlan(user) {
16928
+ const tierPlan = memoryPlanForUser(user);
16929
+ const memoryProPlan = user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
16930
+ return MEMORY_PLAN_RANK[tierPlan] >= MEMORY_PLAN_RANK[memoryProPlan] ? tierPlan : memoryProPlan;
16931
+ }
16892
16932
  async function getOrCreateUserMemoryKey(user) {
16893
16933
  const creds = await getUserMemoryCreds(user.id);
16894
16934
  if (creds.memory_key) {
@@ -16899,10 +16939,9 @@ async function getOrCreateUserMemoryKey(user) {
16899
16939
  if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
16900
16940
  const identity = memoryIdentity(user);
16901
16941
  const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
16902
- const provisioned = await memoryCall(
16903
- "provisionDefaultsTool",
16904
- { granteeIdentity: identity, issueKey: true, plan },
16905
- admin
16942
+ const provisioned = await import_provision_defaults.provisionDefaultsTool.execute(
16943
+ { apiKey: admin, granteeIdentity: identity, issueKey: true, plan },
16944
+ {}
16906
16945
  );
16907
16946
  if (!provisioned.ok || !provisioned.secret) {
16908
16947
  return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
@@ -16929,23 +16968,27 @@ async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
16929
16968
  const admin = ADMIN_KEY();
16930
16969
  if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
16931
16970
  const identity = resolveMemoryIdentity(user);
16932
- const res = await memoryCall("setScheduleEntitlementTool", {
16971
+ const res = await import_set_schedule_entitlement.setScheduleEntitlementTool.execute({
16972
+ apiKey: admin,
16933
16973
  granteeIdentity: identity,
16934
16974
  enabled,
16935
16975
  quotaPerPeriod,
16936
16976
  mcpScraperApiKey: user.api_key
16937
- }, admin);
16977
+ }, {});
16938
16978
  return { ok: res.ok, error: res.error };
16939
16979
  }
16940
- var import_node_crypto6, MEMORY_BASE_URL, ADMIN_KEY;
16980
+ var import_node_crypto6, import_provision_defaults, import_set_schedule_entitlement, MEMORY_BASE_URL, ADMIN_KEY, MEMORY_PLAN_RANK;
16941
16981
  var init_memory = __esm({
16942
16982
  "src/api/memory.ts"() {
16943
16983
  "use strict";
16944
16984
  import_node_crypto6 = require("crypto");
16945
16985
  init_session();
16946
16986
  init_db();
16987
+ import_provision_defaults = require("mcpscraper-memory-tools/tools/vaults/provision-defaults");
16988
+ import_set_schedule_entitlement = require("mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement");
16947
16989
  MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://memory.mcpscraper.dev").replace(/\/$/, "");
16948
16990
  ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
16991
+ MEMORY_PLAN_RANK = { free: 0, pro: 1, team: 2, enterprise: 3 };
16949
16992
  }
16950
16993
  });
16951
16994
 
@@ -17119,6 +17162,33 @@ var init_MapsNavigator = __esm({
17119
17162
  await this.page.waitForSelector('[role="main"] h2', { timeout: 5e3 }).catch(() => {
17120
17163
  });
17121
17164
  }
17165
+ async navigateToSerpBusinessPanel(businessName, location2) {
17166
+ const query = `${businessName} ${location2}`;
17167
+ const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&udm=1`;
17168
+ await this.page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 45e3 }).catch(() => {
17169
+ });
17170
+ await this.page.waitForTimeout(2e3);
17171
+ const clicked = await this.page.evaluate((name) => {
17172
+ const trimmedName = name.trim();
17173
+ const words = trimmedName.split(/\s+/);
17174
+ const prefix = words.slice(0, Math.min(2, words.length)).join(" ");
17175
+ const headings = Array.from(document.querySelectorAll('h3, div[role="heading"]'));
17176
+ const heading = headings.find((el2) => el2.innerText?.trim() === trimmedName) ?? headings.find((el2) => el2.innerText?.trim().startsWith(prefix));
17177
+ const clickable = heading?.closest('a, div[role="link"], div[jsaction]');
17178
+ if (clickable) {
17179
+ clickable.click();
17180
+ return true;
17181
+ }
17182
+ if (heading) {
17183
+ heading.click();
17184
+ return true;
17185
+ }
17186
+ return false;
17187
+ }, businessName);
17188
+ if (!clicked) return false;
17189
+ await this.page.waitForTimeout(2e3);
17190
+ return true;
17191
+ }
17122
17192
  buildReviewsUrl(placeUrl) {
17123
17193
  const fid = placeUrl.match(/!1s(0x[0-9a-f]+:0x[0-9a-f]+)/i);
17124
17194
  const pin = placeUrl.match(/!3d(-?\d+\.\d+)!4d(-?\d+\.\d+)/);
@@ -17305,6 +17375,25 @@ var init_MapsExtractor = __esm({
17305
17375
  }
17306
17376
  await navigator.navigateToAboutTab();
17307
17377
  const aboutAttributes = await this.extractAbout(page);
17378
+ let services = [];
17379
+ let areasServed = [];
17380
+ let servicesStatus = "not_requested";
17381
+ if (options.includeServices) {
17382
+ try {
17383
+ const opened = await navigator.navigateToSerpBusinessPanel(options.businessName, options.location);
17384
+ if (opened) {
17385
+ const result = await this.extractServicesAndAreasServed(page);
17386
+ services = result.services;
17387
+ areasServed = result.areasServed;
17388
+ servicesStatus = services.length > 0 || areasServed.length > 0 ? "collected" : "none_exist";
17389
+ } else {
17390
+ servicesStatus = "unavailable";
17391
+ }
17392
+ } catch (err) {
17393
+ console.warn("[MapsExtractor] services/areas-served extraction failed:", err instanceof Error ? err.message : String(err));
17394
+ servicesStatus = "unavailable";
17395
+ }
17396
+ }
17308
17397
  const reviewCountFinal = overview.reviewCount ?? (reviewStats.reviewHistogram.length > 0 ? reviewStats.reviewHistogram.reduce((s, r) => s + parseInt(r.count, 10), 0).toString() : null);
17309
17398
  return {
17310
17399
  businessName: options.businessName,
@@ -17320,6 +17409,9 @@ var init_MapsExtractor = __esm({
17320
17409
  aboutAttributes,
17321
17410
  reviews,
17322
17411
  reviewsStatus,
17412
+ services,
17413
+ areasServed,
17414
+ servicesStatus,
17323
17415
  durationMs: Date.now() - startMs
17324
17416
  };
17325
17417
  } finally {
@@ -17508,6 +17600,81 @@ var init_MapsExtractor = __esm({
17508
17600
  }
17509
17601
  return result.data;
17510
17602
  }
17603
+ async extractServicesAndAreasServed(page) {
17604
+ await page.waitForTimeout(1500);
17605
+ let services = [];
17606
+ if (await this.clickRowStartingWithRetry(page, "Services:")) {
17607
+ await page.waitForSelector('[role="dialog"]', { timeout: 5e3 }).catch(() => {
17608
+ });
17609
+ await page.waitForTimeout(800);
17610
+ services = await this.extractOpenDialogItems(page, "Services");
17611
+ await this.closeOpenDialog(page, "Services");
17612
+ await page.waitForTimeout(500);
17613
+ }
17614
+ let areasServed = [];
17615
+ if (await this.clickRowStartingWithRetry(page, "Serves ")) {
17616
+ await page.waitForSelector('[role="dialog"]', { timeout: 5e3 }).catch(() => {
17617
+ });
17618
+ await page.waitForTimeout(800);
17619
+ areasServed = await this.extractOpenDialogItems(page, "Areas Served");
17620
+ await this.closeOpenDialog(page, "Areas Served");
17621
+ }
17622
+ return { services, areasServed };
17623
+ }
17624
+ async clickRowStartingWithRetry(page, prefix) {
17625
+ if (await this.clickRowStartingWith(page, prefix)) return true;
17626
+ await page.waitForTimeout(2e3);
17627
+ return this.clickRowStartingWith(page, prefix);
17628
+ }
17629
+ async clickRowStartingWith(page, prefix) {
17630
+ return page.evaluate((p) => {
17631
+ const el2 = Array.from(document.querySelectorAll('button, a, div[role="button"]')).find(
17632
+ (e) => (e.innerText ?? "").trim().startsWith(p)
17633
+ );
17634
+ if (!el2) return false;
17635
+ el2.click();
17636
+ return true;
17637
+ }, prefix);
17638
+ }
17639
+ async extractOpenDialogItems(page, expectedHeading) {
17640
+ return page.evaluate((expected) => {
17641
+ const dialogs = Array.from(document.querySelectorAll('[role="dialog"]'));
17642
+ const withHeading = dialogs.find((d) => {
17643
+ const h = d.querySelector('h1, h2, [role="heading"]');
17644
+ const headingText = h?.innerText?.trim() ?? "";
17645
+ return headingText.startsWith(expected);
17646
+ });
17647
+ if (!withHeading) return [];
17648
+ const headingEl = withHeading.querySelector('h1, h2, [role="heading"]');
17649
+ const headingLines = new Set(
17650
+ (headingEl?.innerText ?? "").split("\n").map((s) => s.trim()).filter(Boolean)
17651
+ );
17652
+ const seen = /* @__PURE__ */ new Set();
17653
+ const items = [];
17654
+ withHeading.querySelectorAll("*").forEach((el2) => {
17655
+ if (el2.children.length > 0) return;
17656
+ const text = el2.innerText?.trim() ?? el2.textContent?.trim() ?? "";
17657
+ if (!text || headingLines.has(text)) return;
17658
+ if (/^(back|close|done|thanks)$/i.test(text)) return;
17659
+ if (/^serves\s.*following/i.test(text)) return;
17660
+ if (seen.has(text)) return;
17661
+ seen.add(text);
17662
+ items.push(text);
17663
+ });
17664
+ return items;
17665
+ }, expectedHeading);
17666
+ }
17667
+ async closeOpenDialog(page, expectedHeading) {
17668
+ await page.evaluate((expected) => {
17669
+ const dialog = Array.from(document.querySelectorAll('[role="dialog"]')).find((d) => {
17670
+ const h = d.querySelector('h1, h2, [role="heading"]');
17671
+ const headingText = h?.innerText?.trim() ?? "";
17672
+ return headingText.startsWith(expected);
17673
+ });
17674
+ const closeBtn = dialog?.querySelector('[aria-label*="Close" i], button[aria-label*="close" i]');
17675
+ closeBtn?.click();
17676
+ }, expectedHeading);
17677
+ }
17511
17678
  };
17512
17679
  }
17513
17680
  });
@@ -18168,6 +18335,17 @@ var init_workflow_catalog = __esm({
18168
18335
  produces: ["content gaps", "page comparison CSV", "PAA questions CSV", "section map", "writer brief"],
18169
18336
  runHint: "Use workflow_run with workflowId serp-comparison for competitor gaps or paa-expansion-brief when the user mainly wants a section map from PAA data."
18170
18337
  },
18338
+ {
18339
+ id: "recurring_tracking_and_memory",
18340
+ title: "Recurring Tracking & Memory",
18341
+ description: "Run a check on a daily, weekly, or custom cadence with no external cron needed, and save each result somewhere searchable so you can compare against past runs later - rankings, prices, competitor changes, anything you would otherwise check by hand and forget to log.",
18342
+ primaryWorkflowId: null,
18343
+ recommendedTools: ["create-scheduled-action", "search_serp", "table-create", "table-insert-rows", "table-query", "memory-put", "memory-search", "list-scheduled-actions"],
18344
+ requiredInputs: ["plain-language description of what to check and how often"],
18345
+ optionalInputs: ["vault to save into", "timeOfDay", "timezone"],
18346
+ produces: ["a live scheduled action that runs automatically on cadence", "a table row or note per run for structured history", "a searchable trend you can compare run-over-run"],
18347
+ runHint: "Use create-scheduled-action with a plain description and cadence; the run itself checks list-vaults, uses table-insert-rows for repeating structured data (e.g. a keyword rank check) or memory-put for a one-off note, and reads prior runs with table-query or memory-search before writing the new one. For a one-time blueprint to build your own external cron/DB instead of a live scheduled action, use rank_tracker_workflow."
18348
+ },
18171
18349
  {
18172
18350
  id: "ai_search_visibility_audit",
18173
18351
  title: "AI Search Visibility Audit",
@@ -19883,6 +20061,9 @@ function formatMapsPlaceIntel(raw, input) {
19883
20061
  const about = d.aboutAttributes ?? [];
19884
20062
  const reviews = d.reviews ?? [];
19885
20063
  const reviewsStatus = d.reviewsStatus ?? "not_requested";
20064
+ const services = d.services ?? [];
20065
+ const areasServed = d.areasServed ?? [];
20066
+ const servicesStatus = d.servicesStatus ?? "not_requested";
19886
20067
  const hoursTable = d.hoursTable ?? [];
19887
20068
  const ratingLine = [rating, reviewCount ? `(${reviewCount} reviews)` : null].filter(Boolean).join(" ");
19888
20069
  const basicLines = [
@@ -19937,6 +20118,20 @@ ${reviews.map((r, i) => {
19937
20118
  ${r.text ?? ""}`;
19938
20119
  }).join("\n\n")}`;
19939
20120
  })();
20121
+ const servicesSection = (() => {
20122
+ if (servicesStatus === "not_requested") return "";
20123
+ if (servicesStatus === "unavailable") return "\n## Services & Areas Served\n> Could not be retrieved this run \u2014 retry with `includeServices: true`.";
20124
+ if (servicesStatus === "none_exist") return "\n## Services & Areas Served\n*Not listed on this business's profile.*";
20125
+ const parts = [
20126
+ services.length ? `**Services**
20127
+ ${services.map((s) => `- ${s}`).join("\n")}` : null,
20128
+ areasServed.length ? `**Areas Served**
20129
+ ${areasServed.map((a) => `- ${a}`).join("\n")}` : null
20130
+ ].filter(Boolean);
20131
+ return parts.length ? `
20132
+ ## Services & Areas Served
20133
+ ${parts.join("\n\n")}` : "";
20134
+ })();
19940
20135
  const full = [
19941
20136
  `# ${name}`,
19942
20137
  category ? `*${category}*` : null,
@@ -19952,6 +20147,7 @@ ${basicLines}` : null,
19952
20147
  ## Entity IDs
19953
20148
  ${entitySection}` : null,
19954
20149
  reviewsSection,
20150
+ servicesSection,
19955
20151
  durationMs != null ? `
19956
20152
  ---
19957
20153
  *Extracted in ${(durationMs / 1e3).toFixed(1)}s*` : null
@@ -19975,7 +20171,10 @@ ${entitySection}` : null,
19975
20171
  lng: lng ?? null,
19976
20172
  reviewsStatus,
19977
20173
  reviewsCollected: reviews.length,
19978
- reviewTopics: topics.map((t) => ({ label: String(t.label ?? ""), count: String(t.count ?? "") }))
20174
+ reviewTopics: topics.map((t) => ({ label: String(t.label ?? ""), count: String(t.count ?? "") })),
20175
+ services,
20176
+ areasServed,
20177
+ servicesStatus
19979
20178
  }
19980
20179
  };
19981
20180
  }
@@ -26325,7 +26524,7 @@ var PACKAGE_VERSION;
26325
26524
  var init_version = __esm({
26326
26525
  "src/version.ts"() {
26327
26526
  "use strict";
26328
- PACKAGE_VERSION = "0.4.0";
26527
+ PACKAGE_VERSION = "0.4.2";
26329
26528
  }
26330
26529
  });
26331
26530
 
@@ -26422,6 +26621,23 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
26422
26621
  answers the question (plain search/extract before browser agents).
26423
26622
  - Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
26424
26623
  read it back for full detail rather than expecting the whole payload inline.
26624
+
26625
+ ## Memory
26626
+ mcp-scraper also exposes 74 persistent per-user memory tools (notes, facts, vaults,
26627
+ scheduled actions, tables, channels) backed by memory.mcpscraper.dev. Every account starts with 13
26628
+ vaults \u2014 call **list-vaults** to see what exists before creating anything new. Pick the vault whose job
26629
+ matches the content: **Ideas** (unvalidated concepts), **Knowledge** (distilled lessons/how-tos),
26630
+ **Library** (raw source material \u2014 articles, transcripts), **People** (one durable note per person/org),
26631
+ **Communications** (individual conversation records), **Calendar** (time-anchored entries), **Tasks**
26632
+ (action items with a status), **Projects** (durable named initiatives), **Issues** (something broken),
26633
+ **Improvement Log** (receipts of changes made and whether they worked), **Experiments** (hypothesis +
26634
+ measured result), **Sprint** (current cycle's scope), **Inspiration** (other people's work worth
26635
+ studying).
26636
+ Before writing, call **memory-suggest** to check whether something's already captured and to find the
26637
+ right vault's hub note to link. Use **memory-put** for narrative notes (prose, a title, a body); use
26638
+ **table-create**/**table-insert-rows**/**table-query** instead when the caller wants row-shaped data
26639
+ they'll filter/sort by exact value. **memory-search** is meaning-based over full content (embeds, slower);
26640
+ **memory-list** filters one vault by kind/tags (fast, exact).
26425
26641
  `.trim();
26426
26642
  }
26427
26643
  var SERVER_INSTRUCTIONS;
@@ -26575,7 +26791,8 @@ var init_mcp_tool_schemas = __esm({
26575
26791
  gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
26576
26792
  hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
26577
26793
  includeReviews: import_zod31.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
26578
- maxReviews: import_zod31.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
26794
+ maxReviews: import_zod31.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500."),
26795
+ includeServices: import_zod31.z.boolean().default(false).describe("Fetch the business's configured services list and areas-served list, when the profile has them. Adds one extra page visit; not present for every business.")
26579
26796
  };
26580
26797
  MapsSearchInputSchema = {
26581
26798
  query: import_zod31.z.string().min(1).describe('Business category, niche, or search term, e.g. "roofers". Do not include location here \u2014 use location instead.'),
@@ -26888,7 +27105,10 @@ var init_mcp_tool_schemas = __esm({
26888
27105
  reviewTopics: import_zod31.z.array(import_zod31.z.object({
26889
27106
  label: import_zod31.z.string(),
26890
27107
  count: import_zod31.z.string()
26891
- }))
27108
+ })),
27109
+ services: import_zod31.z.array(import_zod31.z.string()),
27110
+ areasServed: import_zod31.z.array(import_zod31.z.string()),
27111
+ servicesStatus: import_zod31.z.string()
26892
27112
  };
26893
27113
  CreditsInfoOutputSchema = {
26894
27114
  balanceCredits: import_zod31.z.number().nullable(),
@@ -28714,7 +28934,7 @@ var init_browser_agent_tool_schemas = __esm({
28714
28934
  BrowserProfileConnectOutputSchema = {
28715
28935
  ok: import_zod32.z.boolean(),
28716
28936
  tool: import_zod32.z.literal("browser_profile_connect"),
28717
- session_id: NullableString2,
28937
+ session_id: NullableString2.describe("The underlying live browser session id backing this sign-in."),
28718
28938
  auth_connection_id: import_zod32.z.string(),
28719
28939
  watch_url: import_zod32.z.string().describe("mcpscraper.dev sign-in link to give the user so they can complete this login."),
28720
28940
  live_view_url: NullableString2.describe("Deprecated; always null. Open watch_url to view the sign-in."),
@@ -28724,10 +28944,10 @@ var init_browser_agent_tool_schemas = __esm({
28724
28944
  account_email: NullableString2,
28725
28945
  note: NullableString2,
28726
28946
  status: import_zod32.z.string(),
28727
- flow_status: NullableString2,
28728
- flow_step: NullableString2,
28729
- flow_expires_at: NullableString2,
28730
- post_login_url: NullableString2,
28947
+ flow_status: NullableString2.describe("Deprecated; always null."),
28948
+ flow_step: NullableString2.describe("Deprecated; always null."),
28949
+ flow_expires_at: NullableString2.describe("Deprecated; always null."),
28950
+ post_login_url: NullableString2.describe("Deprecated; always null."),
28731
28951
  connected_logins: import_zod32.z.array(BrowserProfileLogin).describe("Every login currently saved in this profile, so you can see what else is connected."),
28732
28952
  next_steps: import_zod32.z.array(import_zod32.z.string()),
28733
28953
  raw: BrowserRawObject.optional()
@@ -29160,6 +29380,11 @@ function slugPart(value) {
29160
29380
  if (!trimmed) return null;
29161
29381
  return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/[._-]+/g, "-").replace(/^-+|-+$/g, "");
29162
29382
  }
29383
+ function normalizeDomain4(value) {
29384
+ const trimmed = value?.trim().toLowerCase();
29385
+ if (!trimmed) return null;
29386
+ return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
29387
+ }
29163
29388
  function savedProfileNameFromEmail(email) {
29164
29389
  const value = slugPart(email);
29165
29390
  if (!value) return null;
@@ -29269,13 +29494,13 @@ function registerBrowserAgentMcpTools(server, opts) {
29269
29494
  "browser_profile_connect",
29270
29495
  {
29271
29496
  title: "Save a Site Login to a Profile",
29272
- description: "Save a logged-in browser session so this server can reuse a site the user is signed into (ChatGPT, Claude, Reddit, any account-gated site). Returns a watch_url; the user signs in fresh (existing browser cookies are NOT imported) and cookies save to a named profile. ONE profile holds MANY logins \u2014 call again with the same profile and a different domain to stack another account. NOT for one-off scraping (use extract_url) or driving the browser (use browser_open). After sign-in, poll browser_profile_list until AUTHENTICATED, then browser_open with the profile.",
29497
+ description: 'Open a live hosted browser session so the user can sign into a site (ChatGPT, Claude, Reddit, any account-gated site) directly in their own browser tab, then save the login to a named profile. Returns a watch_url \u2014 give it to the user; they sign in fresh on the real site (existing browser cookies are NOT imported), then click "Done" on that page to save the session and close it. ONE profile holds MANY logins \u2014 call again with the same profile and a different domain to stack another account. NOT for one-off scraping (use extract_url) or driving the browser (use browser_open). Billed at the standard live-browser rate while the sign-in session stays open. After the user clicks Done, poll browser_profile_list until AUTHENTICATED, then browser_open with the profile.',
29273
29498
  inputSchema: BrowserProfileConnectInputSchema,
29274
29499
  outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
29275
29500
  annotations: annotations("Save a Site Login to a Profile")
29276
29501
  },
29277
29502
  async (input) => {
29278
- const domain = slugPart(input.domain) || "chatgpt.com";
29503
+ const domain = normalizeDomain4(input.domain) || "chatgpt.com";
29279
29504
  const setupUrl = input.login_url ?? input.url ?? (domain === "chatgpt.com" ? "https://chatgpt.com/" : `https://${domain}/`);
29280
29505
  const profile = input.profile?.trim() || savedProfileNameFromEmail(input.email) || browserServiceProfileName();
29281
29506
  if (!profile) {
@@ -29291,14 +29516,15 @@ function registerBrowserAgentMcpTools(server, opts) {
29291
29516
  domain,
29292
29517
  login_url: setupUrl,
29293
29518
  ...accountEmail ? { account_email: accountEmail } : {},
29294
- ...note ? { note } : {}
29519
+ ...note ? { note } : {},
29520
+ ...typeof input.timeout_seconds === "number" ? { timeout_seconds: input.timeout_seconds } : {}
29295
29521
  });
29296
29522
  if (!open.ok) return errorResult("browser_profile_connect", open.data);
29297
29523
  const connectedLogins = await fetchConnectedLogins(profile).catch(() => []);
29298
29524
  return structuredResult({
29299
29525
  ok: true,
29300
29526
  tool: "browser_profile_connect",
29301
- session_id: null,
29527
+ session_id: open.data.browser_session_id ?? null,
29302
29528
  auth_connection_id: open.data.connection_id,
29303
29529
  watch_url: `${consoleBase}/console/auth/${open.data.connection_id}`,
29304
29530
  live_view_url: null,
@@ -29308,13 +29534,13 @@ function registerBrowserAgentMcpTools(server, opts) {
29308
29534
  account_email: accountEmail,
29309
29535
  note,
29310
29536
  status: open.data.status ?? null,
29311
- flow_status: open.data.flow_status ?? null,
29312
- flow_step: open.data.flow_step ?? null,
29313
- flow_expires_at: open.data.flow_expires_at ?? null,
29314
- post_login_url: open.data.post_login_url ?? null,
29537
+ flow_status: null,
29538
+ flow_step: null,
29539
+ flow_expires_at: null,
29540
+ post_login_url: null,
29315
29541
  connected_logins: connectedLogins,
29316
29542
  next_steps: [
29317
- `Give the user watch_url so they can sign in to ${domain}.`,
29543
+ `Give the user watch_url so they can sign in to ${domain} directly in that page, then click "Done \u2014 I'm signed in".`,
29318
29544
  "Poll browser_profile_list (with this profile) until the login reads AUTHENTICATED.",
29319
29545
  "To add another account to this same profile, call browser_profile_connect again with the same profile and a different domain.",
29320
29546
  "When authenticated, call browser_open with this profile to drive the logged-in session."
@@ -29339,7 +29565,7 @@ function registerBrowserAgentMcpTools(server, opts) {
29339
29565
  error: "profile or email is required when BROWSER_AGENT_PROFILE_NAME is not available"
29340
29566
  });
29341
29567
  }
29342
- const domain = slugPart(input.domain) || void 0;
29568
+ const domain = normalizeDomain4(input.domain) || void 0;
29343
29569
  const res = await req("POST", "/agent/profiles/list", {
29344
29570
  profile,
29345
29571
  ...domain ? { domain } : {},
@@ -31940,6 +32166,486 @@ var init_memory_mcp_tool_executor = __esm({
31940
32166
  }
31941
32167
  });
31942
32168
 
32169
+ // src/mcp/memory-inprocess-executor.ts
32170
+ function registerCutover(upstreamName, handler) {
32171
+ CUTOVER.set(upstreamName, handler);
32172
+ }
32173
+ var CUTOVER, HybridMemoryToolExecutor;
32174
+ var init_memory_inprocess_executor = __esm({
32175
+ "src/mcp/memory-inprocess-executor.ts"() {
32176
+ "use strict";
32177
+ init_memory();
32178
+ CUTOVER = /* @__PURE__ */ new Map();
32179
+ HybridMemoryToolExecutor = class {
32180
+ constructor(httpExecutor, user) {
32181
+ this.httpExecutor = httpExecutor;
32182
+ this.user = user;
32183
+ }
32184
+ httpExecutor;
32185
+ user;
32186
+ mkKeyPromise = null;
32187
+ async resolveMkKey() {
32188
+ if (!this.mkKeyPromise) {
32189
+ this.mkKeyPromise = getOrCreateUserMemoryKey(this.user).then((r) => r.key);
32190
+ }
32191
+ return this.mkKeyPromise;
32192
+ }
32193
+ async callMemoryTool(upstreamName, args) {
32194
+ const handler = CUTOVER.get(upstreamName);
32195
+ if (!handler) return this.httpExecutor.callMemoryTool(upstreamName, args);
32196
+ const mkKey = await this.resolveMkKey();
32197
+ if (!mkKey) return { content: [{ type: "text", text: "memory unavailable" }], isError: true };
32198
+ try {
32199
+ return await handler(args, mkKey);
32200
+ } catch (err) {
32201
+ const message = err instanceof Error ? err.message : `memory ${upstreamName} failed in-process`;
32202
+ return { content: [{ type: "text", text: message }], isError: true };
32203
+ }
32204
+ }
32205
+ };
32206
+ }
32207
+ });
32208
+
32209
+ // src/mcp/memory-cutover/reads.ts
32210
+ function registerReadCutovers() {
32211
+ if (registered) return;
32212
+ registered = true;
32213
+ registerCutover("listApprovedSendersTool", async (input, apiKey) => {
32214
+ const result = await import_list_approved_senders.listApprovedSendersTool.execute({ ...input, apiKey }, {});
32215
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32216
+ });
32217
+ registerCutover("noteInboxTool", async (input, apiKey) => {
32218
+ const result = await import_note_inbox.noteInboxTool.execute({ ...input, apiKey }, {});
32219
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32220
+ });
32221
+ registerCutover("getMessageNoteTool", async (input, apiKey) => {
32222
+ const result = await import_get_message_note.getMessageNoteTool.execute({ ...input, apiKey }, {});
32223
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32224
+ });
32225
+ registerCutover("listChannelMembersTool", async (input, apiKey) => {
32226
+ const result = await import_list_channel_members.listChannelMembersTool.execute({ ...input, apiKey }, {});
32227
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32228
+ });
32229
+ registerCutover("myMentionsTool", async (input, apiKey) => {
32230
+ const result = await import_my_mentions.myMentionsTool.execute({ ...input, apiKey }, {});
32231
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32232
+ });
32233
+ registerCutover("factHistoryTool", async (input, apiKey) => {
32234
+ const result = await import_fact_history.factHistoryTool.execute({ ...input, apiKey }, {});
32235
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32236
+ });
32237
+ registerCutover("exportTool", async (input, apiKey) => {
32238
+ const result = await import_export2.exportTool.execute({ ...input, apiKey }, {});
32239
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32240
+ });
32241
+ registerCutover("getTool", async (input, apiKey) => {
32242
+ const result = await import_get.getTool.execute({ ...input, apiKey }, {});
32243
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32244
+ });
32245
+ registerCutover("listTool", async (input, apiKey) => {
32246
+ const result = await import_list.listTool.execute({ ...input, apiKey }, {});
32247
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32248
+ });
32249
+ registerCutover("searchTool", async (input, apiKey) => {
32250
+ const result = await import_search.searchTool.execute({ ...input, apiKey }, {});
32251
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32252
+ });
32253
+ registerCutover("suggestTool", async (input, apiKey) => {
32254
+ const result = await import_suggest.suggestTool.execute({ ...input, apiKey }, {});
32255
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32256
+ });
32257
+ registerCutover("temporalRecallTool", async (input, apiKey) => {
32258
+ const result = await import_temporal_recall.temporalRecallTool.execute({ ...input, apiKey }, {});
32259
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32260
+ });
32261
+ registerCutover("listScheduledActionsTool", async (input, apiKey) => {
32262
+ const result = await import_list_scheduled_actions.listScheduledActionsTool.execute({ ...input, apiKey }, {});
32263
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32264
+ });
32265
+ registerCutover("costUsageTool", async (input, apiKey) => {
32266
+ const result = await import_cost_usage.costUsageTool.execute({ ...input, apiKey }, {});
32267
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32268
+ });
32269
+ registerCutover("storageUsageTool", async (input, apiKey) => {
32270
+ const result = await import_storage_usage.storageUsageTool.execute({ ...input, apiKey }, {});
32271
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32272
+ });
32273
+ registerCutover("describeTableTool", async (input, apiKey) => {
32274
+ const result = await import_table_describe.describeTableTool.execute({ ...input, apiKey }, {});
32275
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32276
+ });
32277
+ registerCutover("listTablesTool", async (input, apiKey) => {
32278
+ const result = await import_table_list.listTablesTool.execute({ ...input, apiKey }, {});
32279
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32280
+ });
32281
+ registerCutover("queryTableTool", async (input, apiKey) => {
32282
+ const result = await import_table_query.queryTableTool.execute({ ...input, apiKey }, {});
32283
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32284
+ });
32285
+ registerCutover("listSharedWithMeTool", async (input, apiKey) => {
32286
+ const result = await import_list_shared_with_me.listSharedWithMeTool.execute({ ...input, apiKey }, {});
32287
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32288
+ });
32289
+ registerCutover("listVaultsTool", async (input, apiKey) => {
32290
+ const result = await import_list_vaults.listVaultsTool.execute({ ...input, apiKey }, {});
32291
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32292
+ });
32293
+ registerCutover("videoAnalyzeStatusTool", async (input, apiKey) => {
32294
+ const result = await import_status.videoAnalyzeStatusTool.execute({ ...input, apiKey }, {});
32295
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32296
+ });
32297
+ registerCutover("listWebhooksTool", async (input, apiKey) => {
32298
+ const result = await import_list_webhooks.listWebhooksTool.execute({ ...input, apiKey }, {});
32299
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32300
+ });
32301
+ }
32302
+ var import_list_approved_senders, import_note_inbox, import_get_message_note, import_list_channel_members, import_my_mentions, import_fact_history, import_export2, import_get, import_list, import_search, import_suggest, import_temporal_recall, import_list_scheduled_actions, import_cost_usage, import_storage_usage, import_table_describe, import_table_list, import_table_query, import_list_shared_with_me, import_list_vaults, import_status, import_list_webhooks, registered;
32303
+ var init_reads = __esm({
32304
+ "src/mcp/memory-cutover/reads.ts"() {
32305
+ "use strict";
32306
+ init_memory_inprocess_executor();
32307
+ import_list_approved_senders = require("mcpscraper-memory-tools/tools/access/list-approved-senders");
32308
+ import_note_inbox = require("mcpscraper-memory-tools/tools/access/note-inbox");
32309
+ import_get_message_note = require("mcpscraper-memory-tools/tools/channels/get-message-note");
32310
+ import_list_channel_members = require("mcpscraper-memory-tools/tools/channels/list-channel-members");
32311
+ import_my_mentions = require("mcpscraper-memory-tools/tools/channels/my-mentions");
32312
+ import_fact_history = require("mcpscraper-memory-tools/tools/facts/fact-history");
32313
+ import_export2 = require("mcpscraper-memory-tools/tools/memory/export");
32314
+ import_get = require("mcpscraper-memory-tools/tools/memory/get");
32315
+ import_list = require("mcpscraper-memory-tools/tools/memory/list");
32316
+ import_search = require("mcpscraper-memory-tools/tools/memory/search");
32317
+ import_suggest = require("mcpscraper-memory-tools/tools/memory/suggest");
32318
+ import_temporal_recall = require("mcpscraper-memory-tools/tools/recall/temporal-recall");
32319
+ import_list_scheduled_actions = require("mcpscraper-memory-tools/tools/schedule/list-scheduled-actions");
32320
+ import_cost_usage = require("mcpscraper-memory-tools/tools/storage/cost-usage");
32321
+ import_storage_usage = require("mcpscraper-memory-tools/tools/storage/storage-usage");
32322
+ import_table_describe = require("mcpscraper-memory-tools/tools/tables/table-describe");
32323
+ import_table_list = require("mcpscraper-memory-tools/tools/tables/table-list");
32324
+ import_table_query = require("mcpscraper-memory-tools/tools/tables/table-query");
32325
+ import_list_shared_with_me = require("mcpscraper-memory-tools/tools/vaults/list-shared-with-me");
32326
+ import_list_vaults = require("mcpscraper-memory-tools/tools/vaults/list-vaults");
32327
+ import_status = require("mcpscraper-memory-tools/tools/video/status");
32328
+ import_list_webhooks = require("mcpscraper-memory-tools/tools/webhooks/list-webhooks");
32329
+ registered = false;
32330
+ }
32331
+ });
32332
+
32333
+ // src/mcp/memory-cutover/writes.ts
32334
+ function registerWriteCutovers() {
32335
+ if (registered2) return;
32336
+ registered2 = true;
32337
+ registerCutover("acceptShareTool", async (input, apiKey) => {
32338
+ const result = await import_accept_share.acceptShareTool.execute({ ...input, apiKey }, {});
32339
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32340
+ });
32341
+ registerCutover("approveSenderTool", async (input, apiKey) => {
32342
+ const result = await import_approve_sender.approveSenderTool.execute({ ...input, apiKey }, {});
32343
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32344
+ });
32345
+ registerCutover("declineShareTool", async (input, apiKey) => {
32346
+ const result = await import_decline_share.declineShareTool.execute({ ...input, apiKey }, {});
32347
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32348
+ });
32349
+ registerCutover("inboxSettingsTool", async (input, apiKey) => {
32350
+ const result = await import_inbox_settings.inboxSettingsTool.execute({ ...input, apiKey }, {});
32351
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32352
+ });
32353
+ registerCutover("removeApprovedSenderTool", async (input, apiKey) => {
32354
+ const result = await import_remove_approved_sender.removeApprovedSenderTool.execute({ ...input, apiKey }, {});
32355
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32356
+ });
32357
+ registerCutover("revokeShareTool", async (input, apiKey) => {
32358
+ const result = await import_revoke_share.revokeShareTool.execute({ ...input, apiKey }, {});
32359
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32360
+ });
32361
+ registerCutover("setAgentIdentityTool", async (input, apiKey) => {
32362
+ const result = await import_set_agent_identity.setAgentIdentityTool.execute({ ...input, apiKey }, {});
32363
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32364
+ });
32365
+ registerCutover("shareNoteTool", async (input, apiKey) => {
32366
+ const result = await import_share_note.shareNoteTool.execute({ ...input, apiKey }, {});
32367
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32368
+ });
32369
+ registerCutover("shareVaultTool", async (input, apiKey) => {
32370
+ const result = await import_share_vault.shareVaultTool.execute({ ...input, apiKey }, {});
32371
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32372
+ });
32373
+ registerCutover("swapVaultTool", async (input, apiKey) => {
32374
+ const result = await import_swap_vault.swapVaultTool.execute({ ...input, apiKey }, {});
32375
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32376
+ });
32377
+ registerCutover("switchAccountTool", async (input, apiKey) => {
32378
+ const result = await import_switch_account.switchAccountTool.execute({ ...input, apiKey }, {});
32379
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32380
+ });
32381
+ registerCutover("unlinkShareTool", async (input, apiKey) => {
32382
+ const result = await import_unlink_share.unlinkShareTool.execute({ ...input, apiKey }, {});
32383
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32384
+ });
32385
+ registerCutover("memoryQuestionsTool", async (input, apiKey) => {
32386
+ const result = await import_memory_questions.memoryQuestionsTool.execute({ ...input, apiKey }, {});
32387
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32388
+ });
32389
+ registerCutover("createChannelTool", async (input, apiKey) => {
32390
+ const result = await import_create_channel.createChannelTool.execute({ ...input, apiKey }, {});
32391
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32392
+ });
32393
+ registerCutover("listChannelMessagesTool", async (input, apiKey) => {
32394
+ const result = await import_list_channel_messages.listChannelMessagesTool.execute({ ...input, apiKey }, {});
32395
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32396
+ });
32397
+ registerCutover("pollChannelTool", async (input, apiKey) => {
32398
+ const result = await import_poll_channel.pollChannelTool.execute({ ...input, apiKey }, {});
32399
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32400
+ });
32401
+ registerCutover("postMessageTool", async (input, apiKey) => {
32402
+ const result = await import_post_message.postMessageTool.execute({ ...input, apiKey }, {});
32403
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32404
+ });
32405
+ registerCutover("reactMessageTool", async (input, apiKey) => {
32406
+ const result = await import_react_message.reactMessageTool.execute({ ...input, apiKey }, {});
32407
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32408
+ });
32409
+ registerCutover("removeChannelMemberTool", async (input, apiKey) => {
32410
+ const result = await import_remove_channel_member.removeChannelMemberTool.execute({ ...input, apiKey }, {});
32411
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32412
+ });
32413
+ registerCutover("replyMessageTool", async (input, apiKey) => {
32414
+ const result = await import_reply_message.replyMessageTool.execute({ ...input, apiKey }, {});
32415
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32416
+ });
32417
+ registerCutover("recordFactTool", async (input, apiKey) => {
32418
+ const result = await import_record_fact.recordFactTool.execute({ ...input, apiKey }, {});
32419
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32420
+ });
32421
+ registerCutover("libraryIngestTool", async (input, apiKey) => {
32422
+ const result = await import_library_ingest.libraryIngestTool.execute({ ...input, apiKey }, {});
32423
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32424
+ });
32425
+ registerCutover("deleteNoteTool", async (input, apiKey) => {
32426
+ const result = await import_delete_note.deleteNoteTool.execute({ ...input, apiKey }, {});
32427
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32428
+ });
32429
+ registerCutover("putTool", async (input, apiKey) => {
32430
+ const result = await import_put.putTool.execute({ ...input, apiKey }, {});
32431
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32432
+ });
32433
+ registerCutover("uploadTool", async (input, apiKey) => {
32434
+ const result = await import_upload.uploadTool.execute({ ...input, apiKey }, {});
32435
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32436
+ });
32437
+ registerCutover("deleteScheduledActionTool", async (input, apiKey) => {
32438
+ const result = await import_delete_scheduled_action.deleteScheduledActionTool.execute({ ...input, apiKey }, {});
32439
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32440
+ });
32441
+ registerCutover("pauseScheduledActionTool", async (input, apiKey) => {
32442
+ const result = await import_pause_scheduled_action.pauseScheduledActionTool.execute({ ...input, apiKey }, {});
32443
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32444
+ });
32445
+ registerCutover("resumeScheduledActionTool", async (input, apiKey) => {
32446
+ const result = await import_resume_scheduled_action.resumeScheduledActionTool.execute({ ...input, apiKey }, {});
32447
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32448
+ });
32449
+ registerCutover("createTableTool", async (input, apiKey) => {
32450
+ const result = await import_table_create.createTableTool.execute({ ...input, apiKey }, {});
32451
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32452
+ });
32453
+ registerCutover("deleteTableRowsTool", async (input, apiKey) => {
32454
+ const result = await import_table_delete_rows.deleteTableRowsTool.execute({ ...input, apiKey }, {});
32455
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32456
+ });
32457
+ registerCutover("dropTableTool", async (input, apiKey) => {
32458
+ const result = await import_table_drop.dropTableTool.execute({ ...input, apiKey }, {});
32459
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32460
+ });
32461
+ registerCutover("insertTableRowsTool", async (input, apiKey) => {
32462
+ const result = await import_table_insert_rows.insertTableRowsTool.execute({ ...input, apiKey }, {});
32463
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32464
+ });
32465
+ registerCutover("addVaultTool", async (input, apiKey) => {
32466
+ const result = await import_add_vault.addVaultTool.execute({ ...input, apiKey }, {});
32467
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32468
+ });
32469
+ registerCutover("deleteVaultTool", async (input, apiKey) => {
32470
+ const result = await import_delete_vault.deleteVaultTool.execute({ ...input, apiKey }, {});
32471
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32472
+ });
32473
+ }
32474
+ var import_accept_share, import_approve_sender, import_decline_share, import_inbox_settings, import_remove_approved_sender, import_revoke_share, import_set_agent_identity, import_share_note, import_share_vault, import_swap_vault, import_switch_account, import_unlink_share, import_memory_questions, import_create_channel, import_list_channel_messages, import_poll_channel, import_post_message, import_react_message, import_remove_channel_member, import_reply_message, import_record_fact, import_library_ingest, import_delete_note, import_put, import_upload, import_delete_scheduled_action, import_pause_scheduled_action, import_resume_scheduled_action, import_table_create, import_table_delete_rows, import_table_drop, import_table_insert_rows, import_add_vault, import_delete_vault, registered2;
32475
+ var init_writes = __esm({
32476
+ "src/mcp/memory-cutover/writes.ts"() {
32477
+ "use strict";
32478
+ init_memory_inprocess_executor();
32479
+ import_accept_share = require("mcpscraper-memory-tools/tools/access/accept-share");
32480
+ import_approve_sender = require("mcpscraper-memory-tools/tools/access/approve-sender");
32481
+ import_decline_share = require("mcpscraper-memory-tools/tools/access/decline-share");
32482
+ import_inbox_settings = require("mcpscraper-memory-tools/tools/access/inbox-settings");
32483
+ import_remove_approved_sender = require("mcpscraper-memory-tools/tools/access/remove-approved-sender");
32484
+ import_revoke_share = require("mcpscraper-memory-tools/tools/access/revoke-share");
32485
+ import_set_agent_identity = require("mcpscraper-memory-tools/tools/access/set-agent-identity");
32486
+ import_share_note = require("mcpscraper-memory-tools/tools/access/share-note");
32487
+ import_share_vault = require("mcpscraper-memory-tools/tools/access/share-vault");
32488
+ import_swap_vault = require("mcpscraper-memory-tools/tools/access/swap-vault");
32489
+ import_switch_account = require("mcpscraper-memory-tools/tools/access/switch-account");
32490
+ import_unlink_share = require("mcpscraper-memory-tools/tools/access/unlink-share");
32491
+ import_memory_questions = require("mcpscraper-memory-tools/tools/capture/memory-questions");
32492
+ import_create_channel = require("mcpscraper-memory-tools/tools/channels/create-channel");
32493
+ import_list_channel_messages = require("mcpscraper-memory-tools/tools/channels/list-channel-messages");
32494
+ import_poll_channel = require("mcpscraper-memory-tools/tools/channels/poll-channel");
32495
+ import_post_message = require("mcpscraper-memory-tools/tools/channels/post-message");
32496
+ import_react_message = require("mcpscraper-memory-tools/tools/channels/react-message");
32497
+ import_remove_channel_member = require("mcpscraper-memory-tools/tools/channels/remove-channel-member");
32498
+ import_reply_message = require("mcpscraper-memory-tools/tools/channels/reply-message");
32499
+ import_record_fact = require("mcpscraper-memory-tools/tools/facts/record-fact");
32500
+ import_library_ingest = require("mcpscraper-memory-tools/tools/library/library-ingest");
32501
+ import_delete_note = require("mcpscraper-memory-tools/tools/memory/delete-note");
32502
+ import_put = require("mcpscraper-memory-tools/tools/memory/put");
32503
+ import_upload = require("mcpscraper-memory-tools/tools/memory/upload");
32504
+ import_delete_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/delete-scheduled-action");
32505
+ import_pause_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/pause-scheduled-action");
32506
+ import_resume_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/resume-scheduled-action");
32507
+ import_table_create = require("mcpscraper-memory-tools/tools/tables/table-create");
32508
+ import_table_delete_rows = require("mcpscraper-memory-tools/tools/tables/table-delete-rows");
32509
+ import_table_drop = require("mcpscraper-memory-tools/tools/tables/table-drop");
32510
+ import_table_insert_rows = require("mcpscraper-memory-tools/tools/tables/table-insert-rows");
32511
+ import_add_vault = require("mcpscraper-memory-tools/tools/vaults/add-vault");
32512
+ import_delete_vault = require("mcpscraper-memory-tools/tools/vaults/delete-vault");
32513
+ registered2 = false;
32514
+ }
32515
+ });
32516
+
32517
+ // src/mcp/memory-cutover/billing.ts
32518
+ function registerBillingCutovers() {
32519
+ if (registered3) return;
32520
+ registered3 = true;
32521
+ registerCutover("issueKeyTool", async (input, apiKey) => {
32522
+ const result = await import_issue_key.issueKeyTool.execute({ ...input, apiKey }, {});
32523
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32524
+ });
32525
+ registerCutover("listKeysTool", async (input, apiKey) => {
32526
+ const result = await import_list_keys.listKeysTool.execute({ ...input, apiKey }, {});
32527
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32528
+ });
32529
+ registerCutover("revokeKeyTool", async (input, apiKey) => {
32530
+ const result = await import_revoke_key.revokeKeyTool.execute({ ...input, apiKey }, {});
32531
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32532
+ });
32533
+ registerCutover("setScopeTool", async (input, apiKey) => {
32534
+ const result = await import_set_scope.setScopeTool.execute({ ...input, apiKey }, {});
32535
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32536
+ });
32537
+ registerCutover("inviteAccountTool", async (input, apiKey) => {
32538
+ const result = await import_invite_account.inviteAccountTool.execute({ ...input, apiKey }, {});
32539
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32540
+ });
32541
+ registerCutover("getChatLinkTool", async (input, apiKey) => {
32542
+ const result = await import_get_chat_link.getChatLinkTool.execute({ ...input, apiKey }, {});
32543
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32544
+ });
32545
+ registerCutover("revokeChatLinkTool", async (input, apiKey) => {
32546
+ const result = await import_revoke_chat_link.revokeChatLinkTool.execute({ ...input, apiKey }, {});
32547
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32548
+ });
32549
+ registerCutover("provisionDefaultsTool", async (input, apiKey) => {
32550
+ const result = await import_provision_defaults2.provisionDefaultsTool.execute({ ...input, apiKey }, {});
32551
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32552
+ });
32553
+ registerCutover("createSecureVaultTool", async (input, apiKey) => {
32554
+ const result = await import_create_secure_vault.createSecureVaultTool.execute({ ...input, apiKey }, {});
32555
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32556
+ });
32557
+ registerCutover("createScheduledActionTool", async (input, apiKey) => {
32558
+ const result = await import_create_scheduled_action.createScheduledActionTool.execute({ ...input, apiKey }, {});
32559
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32560
+ });
32561
+ registerCutover("getScheduleStatusTool", async (input, apiKey) => {
32562
+ const result = await import_get_schedule_status.getScheduleStatusTool.execute({ ...input, apiKey }, {});
32563
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32564
+ });
32565
+ registerCutover("getScheduleLinkTool", async (input, apiKey) => {
32566
+ const result = await import_get_schedule_link.getScheduleLinkTool.execute({ ...input, apiKey }, {});
32567
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32568
+ });
32569
+ registerCutover("revokeScheduleLinkTool", async (input, apiKey) => {
32570
+ const result = await import_revoke_schedule_link.revokeScheduleLinkTool.execute({ ...input, apiKey }, {});
32571
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32572
+ });
32573
+ registerCutover("setScheduleEntitlementTool", async (input, apiKey) => {
32574
+ const result = await import_set_schedule_entitlement2.setScheduleEntitlementTool.execute({ ...input, apiKey }, {});
32575
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32576
+ });
32577
+ registerCutover("createWebhookTool", async (input, apiKey) => {
32578
+ const result = await import_create_webhook.createWebhookTool.execute({ ...input, apiKey }, {});
32579
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32580
+ });
32581
+ registerCutover("revokeWebhookTool", async (input, apiKey) => {
32582
+ const result = await import_revoke_webhook.revokeWebhookTool.execute({ ...input, apiKey }, {});
32583
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32584
+ });
32585
+ }
32586
+ var import_issue_key, import_list_keys, import_revoke_key, import_set_scope, import_invite_account, import_get_chat_link, import_revoke_chat_link, import_provision_defaults2, import_create_secure_vault, import_create_scheduled_action, import_get_schedule_status, import_get_schedule_link, import_revoke_schedule_link, import_set_schedule_entitlement2, import_create_webhook, import_revoke_webhook, registered3;
32587
+ var init_billing = __esm({
32588
+ "src/mcp/memory-cutover/billing.ts"() {
32589
+ "use strict";
32590
+ init_memory_inprocess_executor();
32591
+ import_issue_key = require("mcpscraper-memory-tools/tools/access/issue-key");
32592
+ import_list_keys = require("mcpscraper-memory-tools/tools/access/list-keys");
32593
+ import_revoke_key = require("mcpscraper-memory-tools/tools/access/revoke-key");
32594
+ import_set_scope = require("mcpscraper-memory-tools/tools/access/set-scope");
32595
+ import_invite_account = require("mcpscraper-memory-tools/tools/access/invite-account");
32596
+ import_get_chat_link = require("mcpscraper-memory-tools/tools/access/get-chat-link");
32597
+ import_revoke_chat_link = require("mcpscraper-memory-tools/tools/access/revoke-chat-link");
32598
+ import_provision_defaults2 = require("mcpscraper-memory-tools/tools/vaults/provision-defaults");
32599
+ import_create_secure_vault = require("mcpscraper-memory-tools/tools/vaults/create-secure-vault");
32600
+ import_create_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/create-scheduled-action");
32601
+ import_get_schedule_status = require("mcpscraper-memory-tools/tools/schedule/get-schedule-status");
32602
+ import_get_schedule_link = require("mcpscraper-memory-tools/tools/schedule/get-schedule-link");
32603
+ import_revoke_schedule_link = require("mcpscraper-memory-tools/tools/schedule/revoke-schedule-link");
32604
+ import_set_schedule_entitlement2 = require("mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement");
32605
+ import_create_webhook = require("mcpscraper-memory-tools/tools/webhooks/create-webhook");
32606
+ import_revoke_webhook = require("mcpscraper-memory-tools/tools/webhooks/revoke-webhook");
32607
+ registered3 = false;
32608
+ }
32609
+ });
32610
+
32611
+ // src/mcp/memory-cutover/cross-service.ts
32612
+ function registerCrossServiceCutovers() {
32613
+ if (registered4) return;
32614
+ registered4 = true;
32615
+ registerCutover("proposeScheduledActionTool", async (input, apiKey) => {
32616
+ const result = await import_propose_scheduled_action.proposeScheduledActionTool.execute({ ...input, apiKey }, {});
32617
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32618
+ });
32619
+ }
32620
+ var import_propose_scheduled_action, registered4;
32621
+ var init_cross_service = __esm({
32622
+ "src/mcp/memory-cutover/cross-service.ts"() {
32623
+ "use strict";
32624
+ init_memory_inprocess_executor();
32625
+ import_propose_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/propose-scheduled-action");
32626
+ registered4 = false;
32627
+ }
32628
+ });
32629
+
32630
+ // src/mcp/memory-cutover/video.ts
32631
+ function registerVideoCutovers() {
32632
+ if (registered5) return;
32633
+ registered5 = true;
32634
+ registerCutover("videoAnalyzeStartTool", async (input, apiKey) => {
32635
+ const result = await import_start.videoAnalyzeStartTool.execute({ ...input, apiKey }, {});
32636
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
32637
+ });
32638
+ }
32639
+ var import_start, registered5;
32640
+ var init_video = __esm({
32641
+ "src/mcp/memory-cutover/video.ts"() {
32642
+ "use strict";
32643
+ init_memory_inprocess_executor();
32644
+ import_start = require("mcpscraper-memory-tools/tools/video/start");
32645
+ registered5 = false;
32646
+ }
32647
+ });
32648
+
31943
32649
  // src/mcp/mcp-oauth.ts
31944
32650
  function oauthIssuer() {
31945
32651
  return (process.env.OAUTH_ISSUER ?? "https://mcpscraper.dev").replace(/\/$/, "");
@@ -32018,13 +32724,13 @@ async function requireMcpCallerKey(c) {
32018
32724
  if (!identity) return mcpAuthError("Bearer token has no subject");
32019
32725
  const user2 = await getUserByEmail(identity);
32020
32726
  if (!user2 || !user2.api_key || user2.key_active !== 1) return mcpAuthError("No active MCP Scraper account for this identity");
32021
- return user2.api_key;
32727
+ return { key: user2.api_key, user: user2 };
32022
32728
  }
32023
32729
  const callerKey = xApiKey ?? bearerToken;
32024
32730
  if (!callerKey) return mcpAuthError();
32025
32731
  const user = await getUserByApiKey(callerKey);
32026
32732
  if (!user) return mcpAuthError();
32027
- return callerKey;
32733
+ return { key: callerKey, user };
32028
32734
  }
32029
32735
  var import_hono14, import_webStandardStreamableHttp, mcpApp;
32030
32736
  var init_mcp_routes = __esm({
@@ -32037,16 +32743,27 @@ var init_mcp_routes = __esm({
32037
32743
  init_browser_agent_mcp_server();
32038
32744
  init_memory_mcp_server();
32039
32745
  init_memory_mcp_tool_executor();
32746
+ init_memory_inprocess_executor();
32747
+ init_reads();
32748
+ init_writes();
32749
+ init_billing();
32750
+ init_cross_service();
32751
+ init_video();
32040
32752
  init_mcp_response_formatter();
32041
32753
  init_db();
32042
32754
  init_mcp_oauth();
32755
+ registerReadCutovers();
32756
+ registerWriteCutovers();
32757
+ registerBillingCutovers();
32758
+ registerCrossServiceCutovers();
32759
+ registerVideoCutovers();
32043
32760
  configureReportSaving(false);
32044
32761
  mcpApp = new import_hono14.Hono();
32045
32762
  mcpApp.all("/", async (c) => {
32046
32763
  try {
32047
32764
  const keyOrError = await requireMcpCallerKey(c);
32048
32765
  if (keyOrError instanceof Response) return keyOrError;
32049
- const callerKey = keyOrError;
32766
+ const { key: callerKey, user } = keyOrError;
32050
32767
  const baseUrl = process.env.MCP_SCRAPER_BASE_URL?.trim() ?? process.env.MCP_BASE_URL?.trim() ?? "https://mcpscraper.dev";
32051
32768
  const executor = new HttpMcpToolExecutor(baseUrl, callerKey);
32052
32769
  const transport = new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
@@ -32057,7 +32774,8 @@ var init_mcp_routes = __esm({
32057
32774
  const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
32058
32775
  registerSerpIntelligenceCaptureTools(server, executor);
32059
32776
  registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
32060
- registerMemoryMcpTools(server, new MemoryMcpToolExecutor(baseUrl, callerKey));
32777
+ const memoryExecutor = new HybridMemoryToolExecutor(new MemoryMcpToolExecutor(baseUrl, callerKey), user);
32778
+ registerMemoryMcpTools(server, memoryExecutor);
32061
32779
  await server.connect(transport);
32062
32780
  return transport.handleRequest(c.req.raw);
32063
32781
  } catch {
@@ -32142,23 +32860,34 @@ async function migrateBrowserAgent() {
32142
32860
  await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN updated_at TEXT`);
32143
32861
  } catch {
32144
32862
  }
32863
+ try {
32864
+ await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN status TEXT NOT NULL DEFAULT 'NEEDS_AUTH'`);
32865
+ } catch {
32866
+ }
32867
+ try {
32868
+ await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN browser_agent_session_id TEXT`);
32869
+ } catch {
32870
+ }
32145
32871
  await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
32146
32872
  _ready2 = true;
32147
32873
  }
32148
- async function saveAuthConnection(input) {
32149
- if (!input.connectionId) return;
32874
+ async function createAuthConnectionRow(input) {
32875
+ const db = getDb();
32876
+ const connectionId = `authc_${(0, import_node_crypto11.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
32877
+ await db.execute({
32878
+ sql: `INSERT INTO browser_auth_connections (connection_id, domain, profile, account_email, note, status, browser_agent_session_id)
32879
+ VALUES (?, ?, ?, ?, ?, 'NEEDS_AUTH', ?)`,
32880
+ args: [connectionId, input.domain, input.profile, input.accountEmail, input.note, input.browserAgentSessionId]
32881
+ });
32882
+ const row = await getAuthConnectionRow(connectionId);
32883
+ if (!row) throw new Error("auth connection insert failed");
32884
+ return row;
32885
+ }
32886
+ async function markAuthConnectionAuthenticated(connectionId) {
32150
32887
  const db = getDb();
32151
32888
  await db.execute({
32152
- sql: `INSERT INTO browser_auth_connections (connection_id, hosted_url, domain, profile, account_email, note, updated_at)
32153
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
32154
- ON CONFLICT(connection_id) DO UPDATE SET
32155
- hosted_url = COALESCE(excluded.hosted_url, browser_auth_connections.hosted_url),
32156
- domain = COALESCE(excluded.domain, browser_auth_connections.domain),
32157
- profile = COALESCE(excluded.profile, browser_auth_connections.profile),
32158
- account_email = COALESCE(excluded.account_email, browser_auth_connections.account_email),
32159
- note = COALESCE(excluded.note, browser_auth_connections.note),
32160
- updated_at = datetime('now')`,
32161
- args: [input.connectionId, input.hostedUrl, input.domain, input.profile, input.accountEmail ?? null, input.note ?? null]
32889
+ sql: `UPDATE browser_auth_connections SET status = 'AUTHENTICATED', updated_at = datetime('now') WHERE connection_id = ?`,
32890
+ args: [connectionId]
32162
32891
  });
32163
32892
  }
32164
32893
  async function getAuthConnectionRow(connectionId) {
@@ -33065,79 +33794,10 @@ async function ensureProfile(k, name) {
33065
33794
  if (!isProfileConflict(err)) throw err;
33066
33795
  }
33067
33796
  }
33068
- function profileConnectionMatches(connection, profileName, domain) {
33069
- if (!connection || typeof connection !== "object") return false;
33070
- const data = connection;
33071
- return data.profile_name === profileName && data.domain === domain;
33072
- }
33073
- async function findProfileConnection(k, profileName, domain) {
33074
- for await (const connection of k.auth.connections.list({ profile_name: profileName, domain })) {
33075
- if (profileConnectionMatches(connection, profileName, domain)) return connection;
33076
- }
33077
- return null;
33078
- }
33079
- async function listProfileConnections(profileName) {
33080
- if (!profileName) return [];
33081
- const k = client();
33082
- const out = [];
33083
- for await (const connection of k.auth.connections.list({ profile_name: profileName })) {
33084
- const data = connection;
33085
- if (data.profile_name && data.profile_name !== profileName) continue;
33086
- out.push(profileOnboardResult(connection));
33087
- }
33088
- return out;
33089
- }
33090
- async function findOrCreateProfileConnection(k, opts) {
33091
- try {
33092
- return await k.auth.connections.create({
33093
- domain: opts.domain,
33094
- profile_name: opts.profileName,
33095
- ...opts.loginUrl ? { login_url: opts.loginUrl } : {}
33096
- });
33097
- } catch (err) {
33098
- if (!isProfileConflict(err)) throw err;
33099
- const existing = await findProfileConnection(k, opts.profileName, opts.domain);
33100
- if (existing) return existing;
33101
- throw err;
33102
- }
33103
- }
33104
- function profileOnboardResult(connection, hostedUrl = null, liveViewUrl = null) {
33105
- return {
33106
- connectionId: String(connection.id),
33107
- profileName: String(connection.profile_name),
33108
- domain: String(connection.domain),
33109
- status: String(connection.status),
33110
- flowStatus: typeof connection.flow_status === "string" ? connection.flow_status : null,
33111
- flowStep: typeof connection.flow_step === "string" ? connection.flow_step : null,
33112
- flowExpiresAt: typeof connection.flow_expires_at === "string" ? connection.flow_expires_at : null,
33113
- hostedUrl: hostedUrl ?? (typeof connection.hosted_url === "string" ? connection.hosted_url : null),
33114
- liveViewUrl: liveViewUrl ?? (typeof connection.live_view_url === "string" ? connection.live_view_url : null),
33115
- browserSessionId: typeof connection.browser_session_id === "string" ? connection.browser_session_id : null,
33116
- postLoginUrl: typeof connection.post_login_url === "string" ? connection.post_login_url : null
33117
- };
33118
- }
33119
33797
  function explicitProxyId(value) {
33120
33798
  const trimmed = value?.trim();
33121
33799
  return trimmed || void 0;
33122
33800
  }
33123
- async function startProfileOnboarding(opts) {
33124
- const k = client();
33125
- const connection = await findOrCreateProfileConnection(k, opts);
33126
- const login = await k.auth.connections.login(connection.id);
33127
- const state = await k.auth.connections.retrieve(connection.id);
33128
- return profileOnboardResult(state, login.hosted_url ?? null, login.live_view_url ?? null);
33129
- }
33130
- async function getProfileOnboardingStatus(input) {
33131
- const k = client();
33132
- let connection = null;
33133
- if (input.connectionId) {
33134
- connection = await k.auth.connections.retrieve(input.connectionId);
33135
- } else if (input.profileName && input.domain) {
33136
- connection = await findProfileConnection(k, input.profileName, input.domain);
33137
- }
33138
- if (!connection) throw new Error("profile auth connection not found");
33139
- return profileOnboardResult(connection);
33140
- }
33141
33801
  async function createSession(opts = {}) {
33142
33802
  const k = client();
33143
33803
  const resolvedProxyId = explicitProxyId(opts.proxyId);
@@ -33488,24 +34148,39 @@ async function charge(sessionId, userId, _startedAtMs) {
33488
34148
  if (res.ok) await setBilledMc(sessionId, owed);
33489
34149
  }
33490
34150
  }
34151
+ async function closeAndSettleSession(row) {
34152
+ if (row.user_id != null) {
34153
+ try {
34154
+ await charge(row.id, row.user_id);
34155
+ } catch {
34156
+ }
34157
+ }
34158
+ try {
34159
+ await closeSession(row.runtime_session_id);
34160
+ } catch {
34161
+ }
34162
+ await markSessionClosed(row.id).catch(() => {
34163
+ });
34164
+ await releaseConcurrencyGate(row.concurrency_lock_id).catch(() => {
34165
+ });
34166
+ }
34167
+ async function completeAuthConnection(connectionId) {
34168
+ const connection = await getAuthConnectionRow(connectionId);
34169
+ if (!connection) return { ok: false, error: "not found" };
34170
+ if (connection.browser_agent_session_id) {
34171
+ const sessionRow = await getSessionRow(connection.browser_agent_session_id);
34172
+ if (sessionRow && sessionRow.status === "open") {
34173
+ await closeAndSettleSession(sessionRow);
34174
+ }
34175
+ }
34176
+ await markAuthConnectionAuthenticated(connectionId);
34177
+ return { ok: true };
34178
+ }
33491
34179
  async function reapIdleBrowserSessions(idleSeconds = 120) {
33492
34180
  const rows = await listIdleOpenSessionRows(idleSeconds);
33493
34181
  let reaped = 0;
33494
34182
  for (const row of rows) {
33495
- if (row.user_id != null) {
33496
- try {
33497
- await charge(row.id, row.user_id);
33498
- } catch {
33499
- }
33500
- }
33501
- try {
33502
- await closeSession(row.runtime_session_id);
33503
- } catch {
33504
- }
33505
- await markSessionClosed(row.id).catch(() => {
33506
- });
33507
- if (row.concurrency_lock_id) await releaseConcurrencyGate(row.concurrency_lock_id).catch(() => {
33508
- });
34183
+ await closeAndSettleSession(row);
33509
34184
  reaped++;
33510
34185
  }
33511
34186
  return { reaped };
@@ -33573,74 +34248,87 @@ function buildBrowserAgentRoutes() {
33573
34248
  });
33574
34249
  app2.use("*", auth);
33575
34250
  app2.post("/profiles/onboard", async (c) => {
34251
+ const user = c.get("user");
34252
+ if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
34253
+ return c.json(insufficientBalanceResponse(Number(user.balance_mc ?? 0), BROWSER_OPEN_MIN_BALANCE_MC), 402);
34254
+ }
33576
34255
  const body = await c.req.json().catch(() => ({}));
33577
34256
  const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
33578
34257
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : "chatgpt.com";
33579
34258
  const loginUrl = typeof body.login_url === "string" ? body.login_url.trim() : void 0;
33580
34259
  const accountEmail = typeof body.account_email === "string" ? body.account_email.trim() || null : null;
33581
34260
  const note = typeof body.note === "string" ? body.note.trim() || null : null;
34261
+ const timeoutSeconds = typeof body.timeout_seconds === "number" ? body.timeout_seconds : void 0;
33582
34262
  if (!profileName) return c.json({ error: "profile is required" }, 400);
33583
34263
  if (!domain) return c.json({ error: "domain is required" }, 400);
34264
+ const gate = await acquireConcurrencyGate(user, "browser_agent_session", {
34265
+ ttlSeconds: browserSessionLockTtlSeconds(timeoutSeconds),
34266
+ metadata: { label: typeof body.label === "string" ? body.label : null }
34267
+ });
34268
+ if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
34269
+ let runtimeSessionId = null;
33584
34270
  try {
33585
- const result = await startProfileOnboarding({ profileName, domain, loginUrl });
33586
- try {
33587
- await saveAuthConnection({
33588
- connectionId: result.connectionId,
33589
- hostedUrl: result.hostedUrl,
33590
- domain: result.domain,
33591
- profile: result.profileName,
33592
- accountEmail,
33593
- note
33594
- });
33595
- } catch {
33596
- }
34271
+ const created = await createSession({
34272
+ timeoutSeconds,
34273
+ profileName,
34274
+ saveProfileChanges: true,
34275
+ startUrl: loginUrl || `https://${domain}/`,
34276
+ disableDefaultProxy: true
34277
+ });
34278
+ runtimeSessionId = created.runtimeSessionId;
34279
+ const sessionRow = await createSessionRow({
34280
+ runtimeSessionId: created.runtimeSessionId,
34281
+ liveViewUrl: created.liveViewUrl,
34282
+ cdpWsUrl: created.cdpWsUrl,
34283
+ label: typeof body.label === "string" ? body.label : `Login setup: ${domain} \u2192 ${profileName}`,
34284
+ userId: user.id,
34285
+ concurrencyLockId: gate.lockId
34286
+ });
34287
+ const connection = await createAuthConnectionRow({
34288
+ domain,
34289
+ profile: profileName,
34290
+ accountEmail,
34291
+ note,
34292
+ browserAgentSessionId: sessionRow.id
34293
+ });
33597
34294
  return c.json({
33598
- connection_id: result.connectionId,
33599
- profile: result.profileName,
33600
- domain: result.domain,
33601
- status: result.status,
33602
- flow_status: result.flowStatus,
33603
- flow_step: result.flowStep,
33604
- flow_expires_at: result.flowExpiresAt,
33605
- browser_session_id: result.browserSessionId,
33606
- post_login_url: result.postLoginUrl
34295
+ connection_id: connection.connection_id,
34296
+ profile: profileName,
34297
+ domain,
34298
+ status: connection.status,
34299
+ browser_session_id: sessionRow.id
33607
34300
  });
33608
34301
  } catch (err) {
34302
+ if (runtimeSessionId) {
34303
+ try {
34304
+ await closeSession(runtimeSessionId);
34305
+ } catch {
34306
+ }
34307
+ }
34308
+ await releaseConcurrencyGate(gate.lockId);
33609
34309
  return c.json(failure(err), 502);
33610
34310
  }
33611
34311
  });
33612
34312
  app2.post("/profiles/status", async (c) => {
33613
34313
  const body = await c.req.json().catch(() => ({}));
33614
- const connectionId = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
33615
- const profileName = typeof body.profile === "string" ? body.profile.trim() : void 0;
34314
+ const connectionId = typeof body.connection_id === "string" ? body.connection_id.trim() : "";
34315
+ const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
33616
34316
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
33617
- try {
33618
- const result = await getProfileOnboardingStatus({ connectionId, profileName, domain });
33619
- if (result.hostedUrl) {
33620
- try {
33621
- await saveAuthConnection({
33622
- connectionId: result.connectionId,
33623
- hostedUrl: result.hostedUrl,
33624
- domain: result.domain,
33625
- profile: result.profileName
33626
- });
33627
- } catch {
33628
- }
33629
- }
33630
- return c.json({
33631
- connection_id: result.connectionId,
33632
- profile: result.profileName,
33633
- domain: result.domain,
33634
- status: result.status,
33635
- flow_status: result.flowStatus,
33636
- flow_step: result.flowStep,
33637
- flow_expires_at: result.flowExpiresAt,
33638
- browser_session_id: result.browserSessionId,
33639
- post_login_url: result.postLoginUrl
33640
- });
33641
- } catch (err) {
33642
- return c.json(failure(err), 404);
33643
- }
34317
+ let row = null;
34318
+ if (connectionId) {
34319
+ row = await getAuthConnectionRow(connectionId);
34320
+ } else if (profileName && domain) {
34321
+ const rows = await listAuthConnectionsByProfile(profileName);
34322
+ row = rows.find((r) => r.domain === domain) ?? null;
34323
+ }
34324
+ if (!row) return c.json({ error: "auth connection not found" }, 404);
34325
+ return c.json({
34326
+ connection_id: row.connection_id,
34327
+ profile: row.profile,
34328
+ domain: row.domain,
34329
+ status: row.status,
34330
+ browser_session_id: row.browser_agent_session_id
34331
+ });
33644
34332
  });
33645
34333
  app2.post("/profiles/list", async (c) => {
33646
34334
  const body = await c.req.json().catch(() => ({}));
@@ -33649,23 +34337,15 @@ function buildBrowserAgentRoutes() {
33649
34337
  const domainFilter = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
33650
34338
  const connectionIdFilter = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
33651
34339
  try {
33652
- const [live, stored] = await Promise.all([
33653
- listProfileConnections(profileName),
33654
- listAuthConnectionsByProfile(profileName).catch(() => [])
33655
- ]);
33656
- const notesByDomain = new Map(stored.map((row) => [row.domain ?? "", row]));
33657
- const notesByConnection = new Map(stored.map((row) => [row.connection_id, row]));
33658
- let connections = live.map((conn) => {
33659
- const record = notesByConnection.get(conn.connectionId) ?? notesByDomain.get(conn.domain);
33660
- return {
33661
- connection_id: conn.connectionId,
33662
- domain: conn.domain,
33663
- status: conn.status,
33664
- account_email: record?.account_email ?? null,
33665
- note: record?.note ?? null,
33666
- last_connected_at: record?.updated_at ?? record?.created_at ?? null
33667
- };
33668
- });
34340
+ const stored = await listAuthConnectionsByProfile(profileName);
34341
+ let connections = stored.map((row) => ({
34342
+ connection_id: row.connection_id,
34343
+ domain: row.domain,
34344
+ status: row.status,
34345
+ account_email: row.account_email,
34346
+ note: row.note,
34347
+ last_connected_at: row.updated_at ?? row.created_at
34348
+ }));
33669
34349
  if (connectionIdFilter) connections = connections.filter((c2) => c2.connection_id === connectionIdFilter);
33670
34350
  if (domainFilter) connections = connections.filter((c2) => c2.domain === domainFilter);
33671
34351
  return c.json({ profile: profileName, connections });
@@ -33740,13 +34420,7 @@ function buildBrowserAgentRoutes() {
33740
34420
  const user = c.get("user");
33741
34421
  const row = await loadOpenSession(c.req.param("id"), user.id);
33742
34422
  if (!row) return c.json({ error: "not found" }, 404);
33743
- await charge(row.id, user.id);
33744
- try {
33745
- await closeSession(row.runtime_session_id);
33746
- } catch {
33747
- }
33748
- await markSessionClosed(row.id);
33749
- await releaseConcurrencyGate(row.concurrency_lock_id);
34423
+ await closeAndSettleSession(row);
33750
34424
  return c.json({ ok: true });
33751
34425
  });
33752
34426
  app2.post("/sessions/:id/goto", async (c) => {
@@ -34046,17 +34720,30 @@ var init_browser_agent_routes = __esm({
34046
34720
  });
34047
34721
 
34048
34722
  // src/api/browser-agent-console.ts
34049
- function renderAuthConsoleHtml(connectionId, hostedUrl) {
34050
- const safeId = String(connectionId).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
34051
- const goUrl = `/console/auth/${encodeURIComponent(connectionId)}/go`;
34052
- const body = hostedUrl ? `<iframe class="stage" allow="clipboard-read; clipboard-write" src="${goUrl}"></iframe>
34053
- <div class="fallback">
34054
- <span>Sign-in window not showing?</span>
34055
- <a class="btn" href="${goUrl}" target="_blank" rel="noopener">Open sign-in in a new tab</a>
34056
- </div>` : `<div class="empty">
34723
+ function renderAuthConsoleHtml(connectionId, info) {
34724
+ const esc2 = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
34725
+ const safeId = esc2(connectionId);
34726
+ const domainLabel = info.domain ? esc2(info.domain) : "this site";
34727
+ let body;
34728
+ if (info.status === "AUTHENTICATED") {
34729
+ body = `<div class="empty">
34730
+ <div class="check">\u2713</div>
34731
+ <div>You're signed in to ${domainLabel}.</div>
34732
+ <div class="muted">This login is saved. You can close this tab.</div>
34733
+ </div>`;
34734
+ } else if (info.sessionOpen && info.liveViewUrl) {
34735
+ body = `<iframe class="stage" allow="clipboard-read; clipboard-write" src="${esc2(info.liveViewUrl)}"></iframe>
34736
+ <div class="footer">
34737
+ <span class="muted">Log into ${domainLabel} directly in the window above, then confirm below.</span>
34738
+ <span class="spacer"></span>
34739
+ <button class="btn" id="done">Done \u2014 I'm signed in</button>
34740
+ </div>`;
34741
+ } else {
34742
+ body = `<div class="empty">
34057
34743
  <div>This sign-in link is no longer active.</div>
34058
34744
  <div class="muted">Ask the assistant to reconnect the profile, then open the new link.</div>
34059
34745
  </div>`;
34746
+ }
34060
34747
  return `<!DOCTYPE html>
34061
34748
  <html lang="en">
34062
34749
  <head>
@@ -34072,9 +34759,11 @@ function renderAuthConsoleHtml(connectionId, hostedUrl) {
34072
34759
  header .muted { color: #6b7689; font-size: 12px; }
34073
34760
  .spacer { flex: 1; }
34074
34761
  .stage { flex: 1; width: 100%; border: 0; display: block; background: #05070b; }
34075
- .fallback { display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-top: 1px solid #1c2230; background: #0f131c; font-size: 13px; color: #8893a7; }
34076
- .btn { background: #2b6cff; color: #fff; border-radius: 7px; padding: 7px 12px; text-decoration: none; font-weight: 500; }
34762
+ .footer { display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-top: 1px solid #1c2230; background: #0f131c; font-size: 13px; color: #8893a7; }
34763
+ .btn { background: #2b6cff; color: #fff; border: 0; border-radius: 7px; padding: 8px 14px; text-decoration: none; font-weight: 500; font: inherit; cursor: pointer; }
34764
+ .btn:disabled { opacity: .6; cursor: default; }
34077
34765
  .empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; color: #8893a7; }
34766
+ .check { font-size: 32px; color: #36d399; }
34078
34767
  .muted { color: #6b7689; font-size: 12px; }
34079
34768
  </style>
34080
34769
  ${ATTRIBUTION_PIXEL_SCRIPT}
@@ -34084,9 +34773,23 @@ ${ATTRIBUTION_PIXEL_SCRIPT}
34084
34773
  <h1>MCP Scraper</h1>
34085
34774
  <span class="muted">Secure sign-in \xB7 ${safeId}</span>
34086
34775
  <span class="spacer"></span>
34087
- <span class="muted">Complete the login below \u2014 your session is saved to your profile.</span>
34776
+ <span class="muted">Log in directly to the real site above \u2014 your session is saved to your profile.</span>
34088
34777
  </header>
34089
34778
  ${body}
34779
+ <script>
34780
+ (function(){
34781
+ var doneBtn = document.getElementById('done');
34782
+ if (!doneBtn) return;
34783
+ doneBtn.onclick = function() {
34784
+ doneBtn.disabled = true;
34785
+ doneBtn.textContent = 'Saving\u2026';
34786
+ fetch('/console/auth/${encodeURIComponent(connectionId)}/complete', { method: 'POST' })
34787
+ .then(function(r){ return r.json().catch(function(){ return {}; }); })
34788
+ .then(function(){ location.reload(); })
34789
+ .catch(function(){ doneBtn.disabled = false; doneBtn.textContent = "Done \u2014 I'm signed in"; });
34790
+ };
34791
+ })();
34792
+ </script>
34090
34793
  </body>
34091
34794
  </html>`;
34092
34795
  }
@@ -34349,8 +35052,9 @@ var init_stripe_routes = __esm({
34349
35052
  const user2 = await resolveUser(sub.customer, sub.customer_email);
34350
35053
  if (user2) {
34351
35054
  const live = sub.status === "active" || sub.status === "trialing";
34352
- await setMemoryPlan(user2.id, live ? mem.plan : "free", live ? sub.id : null);
34353
- await syncMemoryKeyPlan(user2, live ? mem.plan : "free");
35055
+ const newMemoryPlan = live ? mem.plan : "free";
35056
+ await setMemoryPlan(user2.id, newMemoryPlan, live ? sub.id : null);
35057
+ await syncMemoryKeyPlan(user2, resolveEffectiveMemoryPlan({ ...user2, memory_plan: newMemoryPlan }));
34354
35058
  }
34355
35059
  return c.json({ received: true });
34356
35060
  }
@@ -34368,7 +35072,9 @@ var init_stripe_routes = __esm({
34368
35072
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
34369
35073
  const tier = SUBSCRIPTION_TIERS[priceId];
34370
35074
  const live = sub.status === "active" || sub.status === "trialing";
34371
- await setSubscriptionTier(user.id, live ? tier.tier : null, live ? tier.concurrency : 0, live ? sub.id : null);
35075
+ const newTier = live ? tier.tier : null;
35076
+ await setSubscriptionTier(user.id, newTier, live ? tier.concurrency : 0, live ? sub.id : null);
35077
+ await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: newTier }));
34372
35078
  } else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
34373
35079
  await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
34374
35080
  await setConcurrencySubId(user.id, sub.id);
@@ -34381,7 +35087,7 @@ var init_stripe_routes = __esm({
34381
35087
  const user2 = await resolveUserByMemorySubscription(sub.id) ?? await getUserByStripeCustomerId(sub.customer);
34382
35088
  if (user2) {
34383
35089
  await setMemoryPlan(user2.id, "free", null);
34384
- await syncMemoryKeyPlan(user2, "free");
35090
+ await syncMemoryKeyPlan(user2, resolveEffectiveMemoryPlan({ ...user2, memory_plan: "free" }));
34385
35091
  }
34386
35092
  return c.json({ received: true });
34387
35093
  }
@@ -34394,6 +35100,7 @@ var init_stripe_routes = __esm({
34394
35100
  if (!user) return c.json({ received: true });
34395
35101
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
34396
35102
  await setSubscriptionTier(user.id, null, 0, null);
35103
+ await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: null }));
34397
35104
  } else if (priceId === CONCURRENCY_PRICE_ID) {
34398
35105
  await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
34399
35106
  await setConcurrencySubId(user.id, null);
@@ -37973,14 +38680,20 @@ var init_server = __esm({
37973
38680
  const id = c.req.param("id");
37974
38681
  await migrateBrowserAgent();
37975
38682
  const row = await getAuthConnectionRow(id);
37976
- return c.html(renderAuthConsoleHtml(id, row?.hosted_url ?? null));
38683
+ const sessionRow = row?.browser_agent_session_id ? await getSessionRow(row.browser_agent_session_id) : null;
38684
+ return c.html(renderAuthConsoleHtml(id, {
38685
+ domain: row?.domain ?? null,
38686
+ status: row?.status ?? null,
38687
+ liveViewUrl: sessionRow?.live_view_url ?? null,
38688
+ sessionOpen: sessionRow?.status === "open"
38689
+ }));
37977
38690
  });
37978
- app.get("/console/auth/:id/go", async (c) => {
38691
+ app.post("/console/auth/:id/complete", async (c) => {
37979
38692
  const id = c.req.param("id");
37980
38693
  await migrateBrowserAgent();
37981
- const row = await getAuthConnectionRow(id);
37982
- if (!row?.hosted_url) return c.text("This sign-in link is no longer active.", 404);
37983
- return c.redirect(row.hosted_url, 302);
38694
+ const result = await completeAuthConnection(id);
38695
+ if (!result.ok) return c.json({ error: result.error }, 404);
38696
+ return c.json({ ok: true });
37984
38697
  });
37985
38698
  app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
37986
38699
  app.route("/stripe", stripeApp);