mcp-scraper 0.4.1 → 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 (45) hide show
  1. package/dist/bin/api-server.cjs +917 -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 +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +64 -21
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +3 -3
  13. package/dist/bin/paa-harvest.cjs +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-7HICT4GY.js +7 -0
  19. package/dist/chunk-7HICT4GY.js.map +1 -0
  20. package/dist/{chunk-NNEIXK5L.js → chunk-FUWZWKGO.js} +2 -2
  21. package/dist/{chunk-QUOZ3LZG.js → chunk-GFY6XHZS.js} +63 -21
  22. package/dist/chunk-GFY6XHZS.js.map +1 -0
  23. package/dist/{chunk-HBC4C75S.js → chunk-NJK5BTUK.js} +2 -2
  24. package/dist/{chunk-JOEZJL4I.js → chunk-NVUBFCJK.js} +2 -2
  25. package/dist/{chunk-BP27CZ5Q.js → chunk-XGIPATLV.js} +2 -1
  26. package/dist/chunk-XGIPATLV.js.map +1 -0
  27. package/dist/index.cjs +1 -0
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +2 -2
  30. package/dist/{server-U7V6DUSY.js → server-6IYQQK6X.js} +793 -210
  31. package/dist/server-6IYQQK6X.js.map +1 -0
  32. package/dist/{site-extract-repository-2MQBAOX5.js → site-extract-repository-4NVO5KQA.js} +3 -3
  33. package/dist/{worker-RVZXIALX.js → worker-7XRZNLKB.js} +4 -4
  34. package/package.json +2 -1
  35. package/dist/chunk-2ULBZRQQ.js +0 -7
  36. package/dist/chunk-2ULBZRQQ.js.map +0 -1
  37. package/dist/chunk-6TDUY7CA.js.map +0 -1
  38. package/dist/chunk-BP27CZ5Q.js.map +0 -1
  39. package/dist/chunk-QUOZ3LZG.js.map +0 -1
  40. package/dist/server-U7V6DUSY.js.map +0 -1
  41. /package/dist/{chunk-NNEIXK5L.js.map → chunk-FUWZWKGO.js.map} +0 -0
  42. /package/dist/{chunk-HBC4C75S.js.map → chunk-NJK5BTUK.js.map} +0 -0
  43. /package/dist/{chunk-JOEZJL4I.js.map → chunk-NVUBFCJK.js.map} +0 -0
  44. /package/dist/{site-extract-repository-2MQBAOX5.js.map → site-extract-repository-4NVO5KQA.js.map} +0 -0
  45. /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.1";
26527
+ PACKAGE_VERSION = "0.4.2";
26329
26528
  }
26330
26529
  });
26331
26530
 
@@ -26592,7 +26791,8 @@ var init_mcp_tool_schemas = __esm({
26592
26791
  gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
26593
26792
  hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
26594
26793
  includeReviews: import_zod31.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
26595
- 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.")
26596
26796
  };
26597
26797
  MapsSearchInputSchema = {
26598
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.'),
@@ -26905,7 +27105,10 @@ var init_mcp_tool_schemas = __esm({
26905
27105
  reviewTopics: import_zod31.z.array(import_zod31.z.object({
26906
27106
  label: import_zod31.z.string(),
26907
27107
  count: import_zod31.z.string()
26908
- }))
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()
26909
27112
  };
26910
27113
  CreditsInfoOutputSchema = {
26911
27114
  balanceCredits: import_zod31.z.number().nullable(),
@@ -28731,7 +28934,7 @@ var init_browser_agent_tool_schemas = __esm({
28731
28934
  BrowserProfileConnectOutputSchema = {
28732
28935
  ok: import_zod32.z.boolean(),
28733
28936
  tool: import_zod32.z.literal("browser_profile_connect"),
28734
- session_id: NullableString2,
28937
+ session_id: NullableString2.describe("The underlying live browser session id backing this sign-in."),
28735
28938
  auth_connection_id: import_zod32.z.string(),
28736
28939
  watch_url: import_zod32.z.string().describe("mcpscraper.dev sign-in link to give the user so they can complete this login."),
28737
28940
  live_view_url: NullableString2.describe("Deprecated; always null. Open watch_url to view the sign-in."),
@@ -28741,10 +28944,10 @@ var init_browser_agent_tool_schemas = __esm({
28741
28944
  account_email: NullableString2,
28742
28945
  note: NullableString2,
28743
28946
  status: import_zod32.z.string(),
28744
- flow_status: NullableString2,
28745
- flow_step: NullableString2,
28746
- flow_expires_at: NullableString2,
28747
- 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."),
28748
28951
  connected_logins: import_zod32.z.array(BrowserProfileLogin).describe("Every login currently saved in this profile, so you can see what else is connected."),
28749
28952
  next_steps: import_zod32.z.array(import_zod32.z.string()),
28750
28953
  raw: BrowserRawObject.optional()
@@ -29177,6 +29380,11 @@ function slugPart(value) {
29177
29380
  if (!trimmed) return null;
29178
29381
  return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/[._-]+/g, "-").replace(/^-+|-+$/g, "");
29179
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
+ }
29180
29388
  function savedProfileNameFromEmail(email) {
29181
29389
  const value = slugPart(email);
29182
29390
  if (!value) return null;
@@ -29286,13 +29494,13 @@ function registerBrowserAgentMcpTools(server, opts) {
29286
29494
  "browser_profile_connect",
29287
29495
  {
29288
29496
  title: "Save a Site Login to a Profile",
29289
- 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.',
29290
29498
  inputSchema: BrowserProfileConnectInputSchema,
29291
29499
  outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
29292
29500
  annotations: annotations("Save a Site Login to a Profile")
29293
29501
  },
29294
29502
  async (input) => {
29295
- const domain = slugPart(input.domain) || "chatgpt.com";
29503
+ const domain = normalizeDomain4(input.domain) || "chatgpt.com";
29296
29504
  const setupUrl = input.login_url ?? input.url ?? (domain === "chatgpt.com" ? "https://chatgpt.com/" : `https://${domain}/`);
29297
29505
  const profile = input.profile?.trim() || savedProfileNameFromEmail(input.email) || browserServiceProfileName();
29298
29506
  if (!profile) {
@@ -29308,14 +29516,15 @@ function registerBrowserAgentMcpTools(server, opts) {
29308
29516
  domain,
29309
29517
  login_url: setupUrl,
29310
29518
  ...accountEmail ? { account_email: accountEmail } : {},
29311
- ...note ? { note } : {}
29519
+ ...note ? { note } : {},
29520
+ ...typeof input.timeout_seconds === "number" ? { timeout_seconds: input.timeout_seconds } : {}
29312
29521
  });
29313
29522
  if (!open.ok) return errorResult("browser_profile_connect", open.data);
29314
29523
  const connectedLogins = await fetchConnectedLogins(profile).catch(() => []);
29315
29524
  return structuredResult({
29316
29525
  ok: true,
29317
29526
  tool: "browser_profile_connect",
29318
- session_id: null,
29527
+ session_id: open.data.browser_session_id ?? null,
29319
29528
  auth_connection_id: open.data.connection_id,
29320
29529
  watch_url: `${consoleBase}/console/auth/${open.data.connection_id}`,
29321
29530
  live_view_url: null,
@@ -29325,13 +29534,13 @@ function registerBrowserAgentMcpTools(server, opts) {
29325
29534
  account_email: accountEmail,
29326
29535
  note,
29327
29536
  status: open.data.status ?? null,
29328
- flow_status: open.data.flow_status ?? null,
29329
- flow_step: open.data.flow_step ?? null,
29330
- flow_expires_at: open.data.flow_expires_at ?? null,
29331
- 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,
29332
29541
  connected_logins: connectedLogins,
29333
29542
  next_steps: [
29334
- `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".`,
29335
29544
  "Poll browser_profile_list (with this profile) until the login reads AUTHENTICATED.",
29336
29545
  "To add another account to this same profile, call browser_profile_connect again with the same profile and a different domain.",
29337
29546
  "When authenticated, call browser_open with this profile to drive the logged-in session."
@@ -29356,7 +29565,7 @@ function registerBrowserAgentMcpTools(server, opts) {
29356
29565
  error: "profile or email is required when BROWSER_AGENT_PROFILE_NAME is not available"
29357
29566
  });
29358
29567
  }
29359
- const domain = slugPart(input.domain) || void 0;
29568
+ const domain = normalizeDomain4(input.domain) || void 0;
29360
29569
  const res = await req("POST", "/agent/profiles/list", {
29361
29570
  profile,
29362
29571
  ...domain ? { domain } : {},
@@ -31957,6 +32166,486 @@ var init_memory_mcp_tool_executor = __esm({
31957
32166
  }
31958
32167
  });
31959
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
+
31960
32649
  // src/mcp/mcp-oauth.ts
31961
32650
  function oauthIssuer() {
31962
32651
  return (process.env.OAUTH_ISSUER ?? "https://mcpscraper.dev").replace(/\/$/, "");
@@ -32035,13 +32724,13 @@ async function requireMcpCallerKey(c) {
32035
32724
  if (!identity) return mcpAuthError("Bearer token has no subject");
32036
32725
  const user2 = await getUserByEmail(identity);
32037
32726
  if (!user2 || !user2.api_key || user2.key_active !== 1) return mcpAuthError("No active MCP Scraper account for this identity");
32038
- return user2.api_key;
32727
+ return { key: user2.api_key, user: user2 };
32039
32728
  }
32040
32729
  const callerKey = xApiKey ?? bearerToken;
32041
32730
  if (!callerKey) return mcpAuthError();
32042
32731
  const user = await getUserByApiKey(callerKey);
32043
32732
  if (!user) return mcpAuthError();
32044
- return callerKey;
32733
+ return { key: callerKey, user };
32045
32734
  }
32046
32735
  var import_hono14, import_webStandardStreamableHttp, mcpApp;
32047
32736
  var init_mcp_routes = __esm({
@@ -32054,16 +32743,27 @@ var init_mcp_routes = __esm({
32054
32743
  init_browser_agent_mcp_server();
32055
32744
  init_memory_mcp_server();
32056
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();
32057
32752
  init_mcp_response_formatter();
32058
32753
  init_db();
32059
32754
  init_mcp_oauth();
32755
+ registerReadCutovers();
32756
+ registerWriteCutovers();
32757
+ registerBillingCutovers();
32758
+ registerCrossServiceCutovers();
32759
+ registerVideoCutovers();
32060
32760
  configureReportSaving(false);
32061
32761
  mcpApp = new import_hono14.Hono();
32062
32762
  mcpApp.all("/", async (c) => {
32063
32763
  try {
32064
32764
  const keyOrError = await requireMcpCallerKey(c);
32065
32765
  if (keyOrError instanceof Response) return keyOrError;
32066
- const callerKey = keyOrError;
32766
+ const { key: callerKey, user } = keyOrError;
32067
32767
  const baseUrl = process.env.MCP_SCRAPER_BASE_URL?.trim() ?? process.env.MCP_BASE_URL?.trim() ?? "https://mcpscraper.dev";
32068
32768
  const executor = new HttpMcpToolExecutor(baseUrl, callerKey);
32069
32769
  const transport = new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
@@ -32074,7 +32774,8 @@ var init_mcp_routes = __esm({
32074
32774
  const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
32075
32775
  registerSerpIntelligenceCaptureTools(server, executor);
32076
32776
  registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
32077
- registerMemoryMcpTools(server, new MemoryMcpToolExecutor(baseUrl, callerKey));
32777
+ const memoryExecutor = new HybridMemoryToolExecutor(new MemoryMcpToolExecutor(baseUrl, callerKey), user);
32778
+ registerMemoryMcpTools(server, memoryExecutor);
32078
32779
  await server.connect(transport);
32079
32780
  return transport.handleRequest(c.req.raw);
32080
32781
  } catch {
@@ -32159,23 +32860,34 @@ async function migrateBrowserAgent() {
32159
32860
  await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN updated_at TEXT`);
32160
32861
  } catch {
32161
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
+ }
32162
32871
  await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
32163
32872
  _ready2 = true;
32164
32873
  }
32165
- async function saveAuthConnection(input) {
32166
- 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) {
32167
32887
  const db = getDb();
32168
32888
  await db.execute({
32169
- sql: `INSERT INTO browser_auth_connections (connection_id, hosted_url, domain, profile, account_email, note, updated_at)
32170
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
32171
- ON CONFLICT(connection_id) DO UPDATE SET
32172
- hosted_url = COALESCE(excluded.hosted_url, browser_auth_connections.hosted_url),
32173
- domain = COALESCE(excluded.domain, browser_auth_connections.domain),
32174
- profile = COALESCE(excluded.profile, browser_auth_connections.profile),
32175
- account_email = COALESCE(excluded.account_email, browser_auth_connections.account_email),
32176
- note = COALESCE(excluded.note, browser_auth_connections.note),
32177
- updated_at = datetime('now')`,
32178
- 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]
32179
32891
  });
32180
32892
  }
32181
32893
  async function getAuthConnectionRow(connectionId) {
@@ -33082,79 +33794,10 @@ async function ensureProfile(k, name) {
33082
33794
  if (!isProfileConflict(err)) throw err;
33083
33795
  }
33084
33796
  }
33085
- function profileConnectionMatches(connection, profileName, domain) {
33086
- if (!connection || typeof connection !== "object") return false;
33087
- const data = connection;
33088
- return data.profile_name === profileName && data.domain === domain;
33089
- }
33090
- async function findProfileConnection(k, profileName, domain) {
33091
- for await (const connection of k.auth.connections.list({ profile_name: profileName, domain })) {
33092
- if (profileConnectionMatches(connection, profileName, domain)) return connection;
33093
- }
33094
- return null;
33095
- }
33096
- async function listProfileConnections(profileName) {
33097
- if (!profileName) return [];
33098
- const k = client();
33099
- const out = [];
33100
- for await (const connection of k.auth.connections.list({ profile_name: profileName })) {
33101
- const data = connection;
33102
- if (data.profile_name && data.profile_name !== profileName) continue;
33103
- out.push(profileOnboardResult(connection));
33104
- }
33105
- return out;
33106
- }
33107
- async function findOrCreateProfileConnection(k, opts) {
33108
- try {
33109
- return await k.auth.connections.create({
33110
- domain: opts.domain,
33111
- profile_name: opts.profileName,
33112
- ...opts.loginUrl ? { login_url: opts.loginUrl } : {}
33113
- });
33114
- } catch (err) {
33115
- if (!isProfileConflict(err)) throw err;
33116
- const existing = await findProfileConnection(k, opts.profileName, opts.domain);
33117
- if (existing) return existing;
33118
- throw err;
33119
- }
33120
- }
33121
- function profileOnboardResult(connection, hostedUrl = null, liveViewUrl = null) {
33122
- return {
33123
- connectionId: String(connection.id),
33124
- profileName: String(connection.profile_name),
33125
- domain: String(connection.domain),
33126
- status: String(connection.status),
33127
- flowStatus: typeof connection.flow_status === "string" ? connection.flow_status : null,
33128
- flowStep: typeof connection.flow_step === "string" ? connection.flow_step : null,
33129
- flowExpiresAt: typeof connection.flow_expires_at === "string" ? connection.flow_expires_at : null,
33130
- hostedUrl: hostedUrl ?? (typeof connection.hosted_url === "string" ? connection.hosted_url : null),
33131
- liveViewUrl: liveViewUrl ?? (typeof connection.live_view_url === "string" ? connection.live_view_url : null),
33132
- browserSessionId: typeof connection.browser_session_id === "string" ? connection.browser_session_id : null,
33133
- postLoginUrl: typeof connection.post_login_url === "string" ? connection.post_login_url : null
33134
- };
33135
- }
33136
33797
  function explicitProxyId(value) {
33137
33798
  const trimmed = value?.trim();
33138
33799
  return trimmed || void 0;
33139
33800
  }
33140
- async function startProfileOnboarding(opts) {
33141
- const k = client();
33142
- const connection = await findOrCreateProfileConnection(k, opts);
33143
- const login = await k.auth.connections.login(connection.id);
33144
- const state = await k.auth.connections.retrieve(connection.id);
33145
- return profileOnboardResult(state, login.hosted_url ?? null, login.live_view_url ?? null);
33146
- }
33147
- async function getProfileOnboardingStatus(input) {
33148
- const k = client();
33149
- let connection = null;
33150
- if (input.connectionId) {
33151
- connection = await k.auth.connections.retrieve(input.connectionId);
33152
- } else if (input.profileName && input.domain) {
33153
- connection = await findProfileConnection(k, input.profileName, input.domain);
33154
- }
33155
- if (!connection) throw new Error("profile auth connection not found");
33156
- return profileOnboardResult(connection);
33157
- }
33158
33801
  async function createSession(opts = {}) {
33159
33802
  const k = client();
33160
33803
  const resolvedProxyId = explicitProxyId(opts.proxyId);
@@ -33505,24 +34148,39 @@ async function charge(sessionId, userId, _startedAtMs) {
33505
34148
  if (res.ok) await setBilledMc(sessionId, owed);
33506
34149
  }
33507
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
+ }
33508
34179
  async function reapIdleBrowserSessions(idleSeconds = 120) {
33509
34180
  const rows = await listIdleOpenSessionRows(idleSeconds);
33510
34181
  let reaped = 0;
33511
34182
  for (const row of rows) {
33512
- if (row.user_id != null) {
33513
- try {
33514
- await charge(row.id, row.user_id);
33515
- } catch {
33516
- }
33517
- }
33518
- try {
33519
- await closeSession(row.runtime_session_id);
33520
- } catch {
33521
- }
33522
- await markSessionClosed(row.id).catch(() => {
33523
- });
33524
- if (row.concurrency_lock_id) await releaseConcurrencyGate(row.concurrency_lock_id).catch(() => {
33525
- });
34183
+ await closeAndSettleSession(row);
33526
34184
  reaped++;
33527
34185
  }
33528
34186
  return { reaped };
@@ -33590,74 +34248,87 @@ function buildBrowserAgentRoutes() {
33590
34248
  });
33591
34249
  app2.use("*", auth);
33592
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
+ }
33593
34255
  const body = await c.req.json().catch(() => ({}));
33594
34256
  const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
33595
34257
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : "chatgpt.com";
33596
34258
  const loginUrl = typeof body.login_url === "string" ? body.login_url.trim() : void 0;
33597
34259
  const accountEmail = typeof body.account_email === "string" ? body.account_email.trim() || null : null;
33598
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;
33599
34262
  if (!profileName) return c.json({ error: "profile is required" }, 400);
33600
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;
33601
34270
  try {
33602
- const result = await startProfileOnboarding({ profileName, domain, loginUrl });
33603
- try {
33604
- await saveAuthConnection({
33605
- connectionId: result.connectionId,
33606
- hostedUrl: result.hostedUrl,
33607
- domain: result.domain,
33608
- profile: result.profileName,
33609
- accountEmail,
33610
- note
33611
- });
33612
- } catch {
33613
- }
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
+ });
33614
34294
  return c.json({
33615
- connection_id: result.connectionId,
33616
- profile: result.profileName,
33617
- domain: result.domain,
33618
- status: result.status,
33619
- flow_status: result.flowStatus,
33620
- flow_step: result.flowStep,
33621
- flow_expires_at: result.flowExpiresAt,
33622
- browser_session_id: result.browserSessionId,
33623
- 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
33624
34300
  });
33625
34301
  } catch (err) {
34302
+ if (runtimeSessionId) {
34303
+ try {
34304
+ await closeSession(runtimeSessionId);
34305
+ } catch {
34306
+ }
34307
+ }
34308
+ await releaseConcurrencyGate(gate.lockId);
33626
34309
  return c.json(failure(err), 502);
33627
34310
  }
33628
34311
  });
33629
34312
  app2.post("/profiles/status", async (c) => {
33630
34313
  const body = await c.req.json().catch(() => ({}));
33631
- const connectionId = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
33632
- 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() : "";
33633
34316
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
33634
- try {
33635
- const result = await getProfileOnboardingStatus({ connectionId, profileName, domain });
33636
- if (result.hostedUrl) {
33637
- try {
33638
- await saveAuthConnection({
33639
- connectionId: result.connectionId,
33640
- hostedUrl: result.hostedUrl,
33641
- domain: result.domain,
33642
- profile: result.profileName
33643
- });
33644
- } catch {
33645
- }
33646
- }
33647
- return c.json({
33648
- connection_id: result.connectionId,
33649
- profile: result.profileName,
33650
- domain: result.domain,
33651
- status: result.status,
33652
- flow_status: result.flowStatus,
33653
- flow_step: result.flowStep,
33654
- flow_expires_at: result.flowExpiresAt,
33655
- browser_session_id: result.browserSessionId,
33656
- post_login_url: result.postLoginUrl
33657
- });
33658
- } catch (err) {
33659
- return c.json(failure(err), 404);
33660
- }
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
+ });
33661
34332
  });
33662
34333
  app2.post("/profiles/list", async (c) => {
33663
34334
  const body = await c.req.json().catch(() => ({}));
@@ -33666,23 +34337,15 @@ function buildBrowserAgentRoutes() {
33666
34337
  const domainFilter = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
33667
34338
  const connectionIdFilter = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
33668
34339
  try {
33669
- const [live, stored] = await Promise.all([
33670
- listProfileConnections(profileName),
33671
- listAuthConnectionsByProfile(profileName).catch(() => [])
33672
- ]);
33673
- const notesByDomain = new Map(stored.map((row) => [row.domain ?? "", row]));
33674
- const notesByConnection = new Map(stored.map((row) => [row.connection_id, row]));
33675
- let connections = live.map((conn) => {
33676
- const record = notesByConnection.get(conn.connectionId) ?? notesByDomain.get(conn.domain);
33677
- return {
33678
- connection_id: conn.connectionId,
33679
- domain: conn.domain,
33680
- status: conn.status,
33681
- account_email: record?.account_email ?? null,
33682
- note: record?.note ?? null,
33683
- last_connected_at: record?.updated_at ?? record?.created_at ?? null
33684
- };
33685
- });
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
+ }));
33686
34349
  if (connectionIdFilter) connections = connections.filter((c2) => c2.connection_id === connectionIdFilter);
33687
34350
  if (domainFilter) connections = connections.filter((c2) => c2.domain === domainFilter);
33688
34351
  return c.json({ profile: profileName, connections });
@@ -33757,13 +34420,7 @@ function buildBrowserAgentRoutes() {
33757
34420
  const user = c.get("user");
33758
34421
  const row = await loadOpenSession(c.req.param("id"), user.id);
33759
34422
  if (!row) return c.json({ error: "not found" }, 404);
33760
- await charge(row.id, user.id);
33761
- try {
33762
- await closeSession(row.runtime_session_id);
33763
- } catch {
33764
- }
33765
- await markSessionClosed(row.id);
33766
- await releaseConcurrencyGate(row.concurrency_lock_id);
34423
+ await closeAndSettleSession(row);
33767
34424
  return c.json({ ok: true });
33768
34425
  });
33769
34426
  app2.post("/sessions/:id/goto", async (c) => {
@@ -34063,17 +34720,30 @@ var init_browser_agent_routes = __esm({
34063
34720
  });
34064
34721
 
34065
34722
  // src/api/browser-agent-console.ts
34066
- function renderAuthConsoleHtml(connectionId, hostedUrl) {
34067
- const safeId = String(connectionId).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
34068
- const goUrl = `/console/auth/${encodeURIComponent(connectionId)}/go`;
34069
- const body = hostedUrl ? `<iframe class="stage" allow="clipboard-read; clipboard-write" src="${goUrl}"></iframe>
34070
- <div class="fallback">
34071
- <span>Sign-in window not showing?</span>
34072
- <a class="btn" href="${goUrl}" target="_blank" rel="noopener">Open sign-in in a new tab</a>
34073
- </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">
34074
34743
  <div>This sign-in link is no longer active.</div>
34075
34744
  <div class="muted">Ask the assistant to reconnect the profile, then open the new link.</div>
34076
34745
  </div>`;
34746
+ }
34077
34747
  return `<!DOCTYPE html>
34078
34748
  <html lang="en">
34079
34749
  <head>
@@ -34089,9 +34759,11 @@ function renderAuthConsoleHtml(connectionId, hostedUrl) {
34089
34759
  header .muted { color: #6b7689; font-size: 12px; }
34090
34760
  .spacer { flex: 1; }
34091
34761
  .stage { flex: 1; width: 100%; border: 0; display: block; background: #05070b; }
34092
- .fallback { display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-top: 1px solid #1c2230; background: #0f131c; font-size: 13px; color: #8893a7; }
34093
- .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; }
34094
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; }
34095
34767
  .muted { color: #6b7689; font-size: 12px; }
34096
34768
  </style>
34097
34769
  ${ATTRIBUTION_PIXEL_SCRIPT}
@@ -34101,9 +34773,23 @@ ${ATTRIBUTION_PIXEL_SCRIPT}
34101
34773
  <h1>MCP Scraper</h1>
34102
34774
  <span class="muted">Secure sign-in \xB7 ${safeId}</span>
34103
34775
  <span class="spacer"></span>
34104
- <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>
34105
34777
  </header>
34106
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>
34107
34793
  </body>
34108
34794
  </html>`;
34109
34795
  }
@@ -34366,8 +35052,9 @@ var init_stripe_routes = __esm({
34366
35052
  const user2 = await resolveUser(sub.customer, sub.customer_email);
34367
35053
  if (user2) {
34368
35054
  const live = sub.status === "active" || sub.status === "trialing";
34369
- await setMemoryPlan(user2.id, live ? mem.plan : "free", live ? sub.id : null);
34370
- 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 }));
34371
35058
  }
34372
35059
  return c.json({ received: true });
34373
35060
  }
@@ -34385,7 +35072,9 @@ var init_stripe_routes = __esm({
34385
35072
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
34386
35073
  const tier = SUBSCRIPTION_TIERS[priceId];
34387
35074
  const live = sub.status === "active" || sub.status === "trialing";
34388
- 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 }));
34389
35078
  } else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
34390
35079
  await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
34391
35080
  await setConcurrencySubId(user.id, sub.id);
@@ -34398,7 +35087,7 @@ var init_stripe_routes = __esm({
34398
35087
  const user2 = await resolveUserByMemorySubscription(sub.id) ?? await getUserByStripeCustomerId(sub.customer);
34399
35088
  if (user2) {
34400
35089
  await setMemoryPlan(user2.id, "free", null);
34401
- await syncMemoryKeyPlan(user2, "free");
35090
+ await syncMemoryKeyPlan(user2, resolveEffectiveMemoryPlan({ ...user2, memory_plan: "free" }));
34402
35091
  }
34403
35092
  return c.json({ received: true });
34404
35093
  }
@@ -34411,6 +35100,7 @@ var init_stripe_routes = __esm({
34411
35100
  if (!user) return c.json({ received: true });
34412
35101
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
34413
35102
  await setSubscriptionTier(user.id, null, 0, null);
35103
+ await syncMemoryKeyPlan(user, resolveEffectiveMemoryPlan({ ...user, subscription_tier: null }));
34414
35104
  } else if (priceId === CONCURRENCY_PRICE_ID) {
34415
35105
  await setExtraConcurrencySlots(user.id, Math.max(0, user.extra_concurrency_slots - 1));
34416
35106
  await setConcurrencySubId(user.id, null);
@@ -37990,14 +38680,20 @@ var init_server = __esm({
37990
38680
  const id = c.req.param("id");
37991
38681
  await migrateBrowserAgent();
37992
38682
  const row = await getAuthConnectionRow(id);
37993
- 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
+ }));
37994
38690
  });
37995
- app.get("/console/auth/:id/go", async (c) => {
38691
+ app.post("/console/auth/:id/complete", async (c) => {
37996
38692
  const id = c.req.param("id");
37997
38693
  await migrateBrowserAgent();
37998
- const row = await getAuthConnectionRow(id);
37999
- if (!row?.hosted_url) return c.text("This sign-in link is no longer active.", 404);
38000
- 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 });
38001
38697
  });
38002
38698
  app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
38003
38699
  app.route("/stripe", stripeApp);