mcp-scraper 0.3.42 → 0.3.45

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 (39) hide show
  1. package/dist/bin/api-server.cjs +1558 -1312
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +283 -224
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/bin/paa-harvest.cjs.map +1 -1
  14. package/dist/bin/paa-harvest.js +2 -2
  15. package/dist/{chunk-RXJHXBGG.js → chunk-BRVX6RDN.js} +21 -4
  16. package/dist/chunk-BRVX6RDN.js.map +1 -0
  17. package/dist/{chunk-KPF64WST.js → chunk-E7BZFMWB.js} +284 -225
  18. package/dist/chunk-E7BZFMWB.js.map +1 -0
  19. package/dist/{chunk-DVRPXPYH.js → chunk-FUVQR6GK.js} +14 -3
  20. package/dist/chunk-FUVQR6GK.js.map +1 -0
  21. package/dist/chunk-NVTFLLEG.js +7 -0
  22. package/dist/chunk-NVTFLLEG.js.map +1 -0
  23. package/dist/{chunk-GMTS35L6.js → chunk-RXNHY3TF.js} +2 -2
  24. package/dist/{db-LYQENFPW.js → db-H3S3M6KK.js} +2 -2
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +2 -2
  27. package/dist/{server-CZT7EULY.js → server-MKUN6NQZ.js} +513 -370
  28. package/dist/server-MKUN6NQZ.js.map +1 -0
  29. package/dist/{worker-FNSBPP7Y.js → worker-4CVMSUZR.js} +4 -4
  30. package/package.json +4 -2
  31. package/dist/chunk-DVRPXPYH.js.map +0 -1
  32. package/dist/chunk-KPF64WST.js.map +0 -1
  33. package/dist/chunk-OUZZNW3I.js +0 -7
  34. package/dist/chunk-OUZZNW3I.js.map +0 -1
  35. package/dist/chunk-RXJHXBGG.js.map +0 -1
  36. package/dist/server-CZT7EULY.js.map +0 -1
  37. /package/dist/{chunk-GMTS35L6.js.map → chunk-RXNHY3TF.js.map} +0 -0
  38. /package/dist/{db-LYQENFPW.js.map → db-H3S3M6KK.js.map} +0 -0
  39. /package/dist/{worker-FNSBPP7Y.js.map → worker-4CVMSUZR.js.map} +0 -0
@@ -4103,6 +4103,12 @@ async function migrate() {
4103
4103
  )
4104
4104
  `);
4105
4105
  await db.execute(`CREATE INDEX IF NOT EXISTS oauth_tokens_identity ON oauth_tokens(identity)`);
4106
+ for (const col of ["replaced_by TEXT", "rotated_at TEXT"]) {
4107
+ try {
4108
+ await db.execute(`ALTER TABLE oauth_tokens ADD COLUMN ${col}`);
4109
+ } catch {
4110
+ }
4111
+ }
4106
4112
  }
4107
4113
  async function registerClient(clientId, redirectUris, name) {
4108
4114
  await getDb().execute({
@@ -4168,15 +4174,20 @@ async function getRefresh(refreshToken) {
4168
4174
  scope: String(row.scope),
4169
4175
  resource: row.resource != null ? String(row.resource) : null,
4170
4176
  expires_at: String(row.expires_at),
4171
- revoked: Number(row.revoked ?? 0)
4177
+ revoked: Number(row.revoked ?? 0),
4178
+ replaced_by: row.replaced_by != null ? String(row.replaced_by) : null,
4179
+ rotated_at: row.rotated_at != null ? String(row.rotated_at) : null
4172
4180
  };
4173
4181
  }
4174
4182
  async function revokeRefresh(refreshToken) {
4175
4183
  await getDb().execute({ sql: "UPDATE oauth_tokens SET revoked = 1 WHERE refresh_token = ?", args: [refreshToken] });
4176
4184
  }
4177
4185
  async function rotateRefresh(oldToken, next) {
4178
- await revokeRefresh(oldToken);
4179
4186
  await putRefresh(next);
4187
+ await getDb().execute({
4188
+ sql: "UPDATE oauth_tokens SET revoked = 1, replaced_by = ?, rotated_at = ? WHERE refresh_token = ?",
4189
+ args: [next.refresh_token, (/* @__PURE__ */ new Date()).toISOString(), oldToken]
4190
+ });
4180
4191
  }
4181
4192
  async function recordStripeEvent(eventId) {
4182
4193
  const res = await getDb().execute({
@@ -10060,7 +10071,7 @@ function insufficientBalanceResponse(balanceMc, requiredMc) {
10060
10071
  topup_url: topupUrl
10061
10072
  };
10062
10073
  }
10063
- var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, SUBSCRIPTION_TIERS, SUBSCRIPTION_TIER_BY_KEY, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, FREE_SIGNUP_MC, MEMORY_FREE_QUOTA_BYTES, MEMORY_PLANS, MEMORY_PLAN_QUOTA, MEMORY_MARGIN_MULTIPLE, MEMORY_PRO_COST_BUDGET_USD, SCHEDULING_PLANS, LedgerOperation;
10074
+ var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, SUBSCRIPTION_TIERS, SUBSCRIPTION_TIER_BY_KEY, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, FREE_SIGNUP_MC, MEMORY_FREE_QUOTA_BYTES, MEMORY_PLANS, MEMORY_PLAN_QUOTA, MEMORY_MARGIN_MULTIPLE, MEMORY_PRO_COST_BUDGET_USD, SCHEDULING_PLANS, LedgerOperation, MEMORY_AI_MARGIN_MULTIPLE, MC_PER_USD;
10064
10075
  var init_rates = __esm({
10065
10076
  "src/api/rates.ts"() {
10066
10077
  "use strict";
@@ -10085,7 +10096,8 @@ var init_rates = __esm({
10085
10096
  instagram_media: 400,
10086
10097
  instagram_transcribe: 50,
10087
10098
  browser_minute: 12e3,
10088
- reddit_thread: 2e3
10099
+ reddit_thread: 2e3,
10100
+ video_analysis: 666667
10089
10101
  };
10090
10102
  MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 6e4;
10091
10103
  BROWSER_OPEN_MIN_BALANCE_MC = 1e3;
@@ -10248,6 +10260,14 @@ var init_rates = __esm({
10248
10260
  credits: mcToCredits(MC_COSTS.reddit_thread),
10249
10261
  unit: "per thread",
10250
10262
  notes: "Captures a Reddit post and its comment tree. Charged on success; refunded if the thread cannot be retrieved."
10263
+ },
10264
+ {
10265
+ key: "video_analysis",
10266
+ label: "Video breakdown (frame-by-frame + transcript)",
10267
+ aliases: ["video_frame_analysis", "video breakdown", "video analysis", "analyze video"],
10268
+ credits: mcToCredits(MC_COSTS.video_analysis),
10269
+ unit: "per video",
10270
+ notes: "Full multi-lens video breakdown: samples up to 120 frames across a video (to 30 min), 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. Flat $1 per video; refunded if the run fails."
10251
10271
  }
10252
10272
  ];
10253
10273
  CONCURRENCY_PRICE_ID = "price_1Ta1NRS8aAcsk3TGwsRnYbix";
@@ -10320,8 +10340,14 @@ var init_rates = __esm({
10320
10340
  INSTAGRAM_TRANSCRIBE_REFUND: "instagram_transcribe_refund",
10321
10341
  BROWSER_SESSION: "browser_session",
10322
10342
  REDDIT_THREAD: "reddit_thread",
10323
- REDDIT_THREAD_REFUND: "reddit_thread_refund"
10343
+ REDDIT_THREAD_REFUND: "reddit_thread_refund",
10344
+ VIDEO_ANALYSIS: "video_analysis",
10345
+ VIDEO_ANALYSIS_REFUND: "video_analysis_refund",
10346
+ MEMORY_AI: "memory_ai",
10347
+ MEMORY_AI_REFUND: "memory_ai_refund"
10324
10348
  };
10349
+ MEMORY_AI_MARGIN_MULTIPLE = 3;
10350
+ MC_PER_USD = SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].credits_mc / SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].monthly_usd;
10325
10351
  }
10326
10352
  });
10327
10353
 
@@ -11188,6 +11214,34 @@ var init_site_audit_routes = __esm({
11188
11214
  }
11189
11215
  });
11190
11216
 
11217
+ // src/api/internal-memory-routes.ts
11218
+ var import_hono2, internalMemoryApp;
11219
+ var init_internal_memory_routes = __esm({
11220
+ "src/api/internal-memory-routes.ts"() {
11221
+ "use strict";
11222
+ import_hono2 = require("hono");
11223
+ init_db();
11224
+ init_rates();
11225
+ internalMemoryApp = new import_hono2.Hono();
11226
+ internalMemoryApp.post("/ai-debit", async (c) => {
11227
+ const secret2 = c.req.header("x-internal-secret");
11228
+ if (!secret2 || secret2 !== process.env.INTERNAL_MEMORY_DEBIT_SECRET) {
11229
+ return c.json({ error: "unauthorized" }, 401);
11230
+ }
11231
+ const body = await c.req.json().catch(() => ({}));
11232
+ if (!body.identity || typeof body.costUsd !== "number" || body.costUsd < 0) {
11233
+ return c.json({ error: "invalid_request" }, 400);
11234
+ }
11235
+ const user = await getUserByEmail(body.identity.trim().toLowerCase());
11236
+ if (!user) return c.json({ ok: false, reason: "no_user" }, 200);
11237
+ const mc = Math.round(body.costUsd * MEMORY_AI_MARGIN_MULTIPLE * MC_PER_USD);
11238
+ if (mc <= 0) return c.json({ ok: true, mc: 0, balance_mc: null }, 200);
11239
+ const { ok, balance_mc } = await debitMc(user.id, mc, LedgerOperation.MEMORY_AI, `${body.source ?? "ai"}:${body.op ?? ""}`);
11240
+ return c.json({ ok, mc, balance_mc, insufficient: !ok }, 200);
11241
+ });
11242
+ }
11243
+ });
11244
+
11191
11245
  // src/youtube/schemas.ts
11192
11246
  var import_zod12, YouTubeHarvestOptionsSchema;
11193
11247
  var init_schemas2 = __esm({
@@ -13012,11 +13066,11 @@ ${chunks}` : ""}
13012
13066
  function fmtTs(secs) {
13013
13067
  return `${Math.floor(secs / 60)}:${String(Math.floor(secs % 60)).padStart(2, "0")}`;
13014
13068
  }
13015
- var import_hono2, youtubeApp;
13069
+ var import_hono3, youtubeApp;
13016
13070
  var init_youtube_routes = __esm({
13017
13071
  "src/api/youtube-routes.ts"() {
13018
13072
  "use strict";
13019
- import_hono2 = require("hono");
13073
+ import_hono3 = require("hono");
13020
13074
  init_db();
13021
13075
  init_rates();
13022
13076
  init_youtube_harvest();
@@ -13027,7 +13081,7 @@ var init_youtube_routes = __esm({
13027
13081
  init_api_auth();
13028
13082
  init_server_schemas();
13029
13083
  init_concurrency_gates();
13030
- youtubeApp = new import_hono2.Hono();
13084
+ youtubeApp = new import_hono3.Hono();
13031
13085
  youtubeApp.post("/harvest", createApiKeyAuth(), async (c) => {
13032
13086
  const raw = await c.req.json().catch(() => ({}));
13033
13087
  const parsed = YoutubeHarvestBodySchema.safeParse(raw);
@@ -13140,11 +13194,11 @@ var init_youtube_routes = __esm({
13140
13194
  });
13141
13195
 
13142
13196
  // src/api/screenshot-routes.ts
13143
- var import_hono3, import_zod14, ScreenshotBodySchema, screenshotApp;
13197
+ var import_hono4, import_zod14, ScreenshotBodySchema, screenshotApp;
13144
13198
  var init_screenshot_routes = __esm({
13145
13199
  "src/api/screenshot-routes.ts"() {
13146
13200
  "use strict";
13147
- import_hono3 = require("hono");
13201
+ import_hono4 = require("hono");
13148
13202
  init_browser_service_env();
13149
13203
  import_zod14 = require("zod");
13150
13204
  init_screenshot();
@@ -13158,7 +13212,7 @@ var init_screenshot_routes = __esm({
13158
13212
  device: import_zod14.z.string().trim().optional(),
13159
13213
  allowLocal: import_zod14.z.boolean().optional()
13160
13214
  });
13161
- screenshotApp = new import_hono3.Hono();
13215
+ screenshotApp = new import_hono4.Hono();
13162
13216
  screenshotApp.use("*", createApiKeyAuth());
13163
13217
  screenshotApp.post("/", async (c) => {
13164
13218
  const raw = await c.req.json().catch(() => ({}));
@@ -14865,11 +14919,11 @@ async function kernelLaunchOptsResidential() {
14865
14919
  }
14866
14920
  return { headless: true, kernelApiKey: browserServiceApiKey(), kernelProxyId: proxyId, viewport: { width: 1280, height: 900 }, locale: "en-US" };
14867
14921
  }
14868
- var import_hono4, import_zod16, import_client5, FacebookAdBodySchema, FacebookPageIntelBodySchema, FacebookTranscribeBodySchema, FacebookVideoTranscribeBodySchema, FacebookSearchBodySchema, FacebookMediaBodySchema, facebookAdApp, ALLOWED_MEDIA_HOSTS;
14922
+ var import_hono5, import_zod16, import_client5, FacebookAdBodySchema, FacebookPageIntelBodySchema, FacebookTranscribeBodySchema, FacebookVideoTranscribeBodySchema, FacebookSearchBodySchema, FacebookMediaBodySchema, facebookAdApp, ALLOWED_MEDIA_HOSTS;
14869
14923
  var init_facebook_ad_routes = __esm({
14870
14924
  "src/api/facebook-ad-routes.ts"() {
14871
14925
  "use strict";
14872
- import_hono4 = require("hono");
14926
+ import_hono5 = require("hono");
14873
14927
  init_browser_service_env();
14874
14928
  import_zod16 = require("zod");
14875
14929
  init_db();
@@ -14915,7 +14969,7 @@ var init_facebook_ad_routes = __esm({
14915
14969
  url: import_zod16.z.string().trim().min(1, "url is required"),
14916
14970
  filename: import_zod16.z.string().trim().optional()
14917
14971
  });
14918
- facebookAdApp = new import_hono4.Hono();
14972
+ facebookAdApp = new import_hono5.Hono();
14919
14973
  facebookAdApp.post("/ad", createApiKeyAuth(), async (c) => {
14920
14974
  const raw = await c.req.json().catch(() => ({}));
14921
14975
  const parsed = FacebookAdBodySchema.safeParse(raw);
@@ -15446,11 +15500,11 @@ async function kernelLaunchOptsResidential2() {
15446
15500
  function isBlockedMessage(msg) {
15447
15501
  return msg.toLowerCase().includes("blocked") || msg.toLowerCase().includes("captcha");
15448
15502
  }
15449
- var import_hono5, import_zod17, import_client6, GoogleAdsSearchBodySchema, GoogleAdsPageIntelBodySchema, GoogleAdsTranscribeBodySchema, googleAdsApp;
15503
+ var import_hono6, import_zod17, import_client6, GoogleAdsSearchBodySchema, GoogleAdsPageIntelBodySchema, GoogleAdsTranscribeBodySchema, googleAdsApp;
15450
15504
  var init_google_ads_routes = __esm({
15451
15505
  "src/api/google-ads-routes.ts"() {
15452
15506
  "use strict";
15453
- import_hono5 = require("hono");
15507
+ import_hono6 = require("hono");
15454
15508
  import_zod17 = require("zod");
15455
15509
  init_browser_service_env();
15456
15510
  init_db();
@@ -15478,7 +15532,7 @@ var init_google_ads_routes = __esm({
15478
15532
  GoogleAdsTranscribeBodySchema = import_zod17.z.object({
15479
15533
  videoUrl: import_zod17.z.string().trim().min(1, "videoUrl is required")
15480
15534
  });
15481
- googleAdsApp = new import_hono5.Hono();
15535
+ googleAdsApp = new import_hono6.Hono();
15482
15536
  googleAdsApp.post("/search", createApiKeyAuth(), async (c) => {
15483
15537
  const raw = await c.req.json().catch(() => ({}));
15484
15538
  const parsed = GoogleAdsSearchBodySchema.safeParse(raw);
@@ -16053,11 +16107,11 @@ function trackFilename(track, index, shortcode) {
16053
16107
  const bitrate = track.bitrate ? `-${track.bitrate}` : "";
16054
16108
  return `${label}-${type}-${index}${bitrate}.mp4`;
16055
16109
  }
16056
- var import_hono6, import_zod18, import_node_fs4, import_node_os4, import_node_path6, import_node_stream2, import_promises4, import_node_child_process2, InstagramProfileContentBodySchema, InstagramMediaTypeSchema, InstagramMediaDownloadBodySchema, instagramApp;
16110
+ var import_hono7, import_zod18, import_node_fs4, import_node_os4, import_node_path6, import_node_stream2, import_promises4, import_node_child_process2, InstagramProfileContentBodySchema, InstagramMediaTypeSchema, InstagramMediaDownloadBodySchema, instagramApp;
16057
16111
  var init_instagram_routes = __esm({
16058
16112
  "src/api/instagram-routes.ts"() {
16059
16113
  "use strict";
16060
- import_hono6 = require("hono");
16114
+ import_hono7 = require("hono");
16061
16115
  import_zod18 = require("zod");
16062
16116
  import_node_fs4 = require("fs");
16063
16117
  import_node_os4 = require("os");
@@ -16099,7 +16153,7 @@ var init_instagram_routes = __esm({
16099
16153
  includeTranscript: import_zod18.z.boolean().default(false),
16100
16154
  mux: import_zod18.z.boolean().default(true)
16101
16155
  });
16102
- instagramApp = new import_hono6.Hono();
16156
+ instagramApp = new import_hono7.Hono();
16103
16157
  instagramApp.post("/profile-content", createApiKeyAuth(), async (c) => {
16104
16158
  const raw = await c.req.json().catch(() => ({}));
16105
16159
  const parsed = InstagramProfileContentBodySchema.safeParse(raw);
@@ -16304,11 +16358,11 @@ async function residentialProxyId(attemptIndex) {
16304
16358
  return void 0;
16305
16359
  }
16306
16360
  }
16307
- var import_hono7, import_zod19, RedditThreadBodySchema, PARSE_REDDIT, redditApp;
16361
+ var import_hono8, import_zod19, RedditThreadBodySchema, PARSE_REDDIT, redditApp;
16308
16362
  var init_reddit_routes = __esm({
16309
16363
  "src/api/reddit-routes.ts"() {
16310
16364
  "use strict";
16311
- import_hono7 = require("hono");
16365
+ import_hono8 = require("hono");
16312
16366
  import_zod19 = require("zod");
16313
16367
  init_BrowserDriver();
16314
16368
  init_kernel_proxy_resolver();
@@ -16346,7 +16400,7 @@ var init_reddit_routes = __esm({
16346
16400
  });
16347
16401
  return { title: title, author: author, score: score, postBody: postBody, blocked: blocked, numComments: comments.length, comments: comments };
16348
16402
  })()`;
16349
- redditApp = new import_hono7.Hono();
16403
+ redditApp = new import_hono8.Hono();
16350
16404
  redditApp.post("/thread", createApiKeyAuth(), async (c) => {
16351
16405
  const raw = await c.req.json().catch(() => ({}));
16352
16406
  const parsed = RedditThreadBodySchema.safeParse(raw);
@@ -16380,11 +16434,11 @@ var init_reddit_routes = __esm({
16380
16434
  locale: "en-US"
16381
16435
  });
16382
16436
  const page = driver.getPage();
16383
- await page.goto(oldUrl, { waitUntil: "domcontentloaded", timeout: 45e3 });
16437
+ await page.goto(oldUrl, { waitUntil: "domcontentloaded", timeout: attempt === 0 ? 3e4 : 45e3 });
16384
16438
  await page.waitForTimeout(2500);
16385
16439
  const data = await page.evaluate(PARSE_REDDIT);
16386
16440
  if (!data.blocked && (data.postBody || data.comments.length > 0)) {
16387
- result = { ...data, sourceUrl: body.url, oldRedditUrl: oldUrl, attempts: attempt + 1 };
16441
+ result = { ...data, sourceUrl: body.url, oldRedditUrl: oldUrl, attempts: attempt + 1, proxied: Boolean(proxyId) };
16388
16442
  }
16389
16443
  } catch {
16390
16444
  } finally {
@@ -16415,6 +16469,249 @@ var init_reddit_routes = __esm({
16415
16469
  }
16416
16470
  });
16417
16471
 
16472
+ // src/api/session.ts
16473
+ function getSessionSecret() {
16474
+ const configured = process.env.SESSION_SECRET?.trim();
16475
+ if (configured) return configured;
16476
+ if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
16477
+ return "dev-secret-change-me";
16478
+ }
16479
+ function safeEqualHex(a, b) {
16480
+ if (a.length !== b.length) return false;
16481
+ try {
16482
+ return (0, import_node_crypto5.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
16483
+ } catch {
16484
+ return false;
16485
+ }
16486
+ }
16487
+ function signSession(userId) {
16488
+ const payload = String(userId);
16489
+ const sig = (0, import_node_crypto5.createHmac)("sha256", secret()).update(payload).digest("hex");
16490
+ return `${payload}.${sig}`;
16491
+ }
16492
+ function verifySession(token) {
16493
+ const dot = token.lastIndexOf(".");
16494
+ if (dot === -1) return null;
16495
+ const payload = token.slice(0, dot);
16496
+ const sig = token.slice(dot + 1);
16497
+ const expected = (0, import_node_crypto5.createHmac)("sha256", secret()).update(payload).digest("hex");
16498
+ if (!safeEqualHex(sig, expected)) return null;
16499
+ const id = parseInt(payload);
16500
+ return isNaN(id) ? null : id;
16501
+ }
16502
+ var import_node_crypto5, isProduction, secret;
16503
+ var init_session = __esm({
16504
+ "src/api/session.ts"() {
16505
+ "use strict";
16506
+ import_node_crypto5 = require("crypto");
16507
+ isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
16508
+ secret = () => getSessionSecret();
16509
+ }
16510
+ });
16511
+
16512
+ // src/api/memory.ts
16513
+ function isMemoryOperator(email) {
16514
+ if (!email) return false;
16515
+ const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
16516
+ return ops.includes(email.trim().toLowerCase());
16517
+ }
16518
+ function encKey() {
16519
+ return (0, import_node_crypto6.scryptSync)(getSessionSecret(), "mcp-memory-key-v1", 32);
16520
+ }
16521
+ function encryptMemoryKey(secret2) {
16522
+ const iv = (0, import_node_crypto6.randomBytes)(12);
16523
+ const cipher = (0, import_node_crypto6.createCipheriv)("aes-256-gcm", encKey(), iv);
16524
+ const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
16525
+ const tag = cipher.getAuthTag();
16526
+ return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
16527
+ }
16528
+ function decryptMemoryKey(stored) {
16529
+ try {
16530
+ const [ivB, tagB, dataB] = stored.split(":");
16531
+ const decipher = (0, import_node_crypto6.createDecipheriv)("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
16532
+ decipher.setAuthTag(Buffer.from(tagB, "base64"));
16533
+ return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
16534
+ } catch {
16535
+ return null;
16536
+ }
16537
+ }
16538
+ async function memoryCall(toolName, args, userMemoryKey) {
16539
+ try {
16540
+ const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
16541
+ method: "POST",
16542
+ headers: { "content-type": "application/json" },
16543
+ body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
16544
+ });
16545
+ const body = await res.json().catch(() => null);
16546
+ if (!res.ok) return { ok: false, error: body?.error ?? `memory ${toolName} failed (${res.status})` };
16547
+ if (body?.result) return body.result;
16548
+ return { ok: false, error: body?.error ?? `memory ${toolName} returned no result` };
16549
+ } catch (err) {
16550
+ return { ok: false, error: err?.message ?? "memory call failed" };
16551
+ }
16552
+ }
16553
+ function personalVault(user) {
16554
+ return `mcp-${user.id}`;
16555
+ }
16556
+ function memoryIdentity(user) {
16557
+ return user.email;
16558
+ }
16559
+ function resolveMemoryIdentity(user) {
16560
+ return memoryIdentity(user);
16561
+ }
16562
+ function memoryPlanForReads(user) {
16563
+ if (isMemoryOperator(user.email)) return "unlimited";
16564
+ if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
16565
+ return "free";
16566
+ }
16567
+ async function provisionMemoryForUser(user) {
16568
+ try {
16569
+ const { key } = await getOrCreateUserMemoryKey(user);
16570
+ if (key) await setMemoryProvisioned(user.id, true);
16571
+ } catch {
16572
+ }
16573
+ }
16574
+ async function getOrCreateUserMemoryKey(user) {
16575
+ const creds = await getUserMemoryCreds(user.id);
16576
+ if (creds.memory_key) {
16577
+ const decrypted = decryptMemoryKey(creds.memory_key);
16578
+ if (decrypted) return { key: decrypted };
16579
+ }
16580
+ const admin = ADMIN_KEY();
16581
+ if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
16582
+ const identity = memoryIdentity(user);
16583
+ const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
16584
+ const provisioned = await memoryCall(
16585
+ "provisionDefaultsTool",
16586
+ { granteeIdentity: identity, issueKey: true, plan },
16587
+ admin
16588
+ );
16589
+ if (!provisioned.ok || !provisioned.secret) {
16590
+ return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
16591
+ }
16592
+ await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
16593
+ return { key: provisioned.secret };
16594
+ }
16595
+ async function syncMemoryKeyPlan(user, plan) {
16596
+ const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
16597
+ const { key, error } = await getOrCreateUserMemoryKey(user);
16598
+ if (!key) return { ok: false, error: error ?? "memory unavailable" };
16599
+ const listed = await memoryCall(
16600
+ "listKeysTool",
16601
+ {},
16602
+ key
16603
+ );
16604
+ if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
16605
+ const target = listed.keys[0];
16606
+ if (target.plan === effectivePlan) return { ok: true };
16607
+ const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
16608
+ return { ok: res.ok, error: res.error };
16609
+ }
16610
+ async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
16611
+ const admin = ADMIN_KEY();
16612
+ if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
16613
+ const identity = resolveMemoryIdentity(user);
16614
+ const res = await memoryCall("setScheduleEntitlementTool", {
16615
+ granteeIdentity: identity,
16616
+ enabled,
16617
+ quotaPerPeriod,
16618
+ mcpScraperApiKey: user.api_key
16619
+ }, admin);
16620
+ return { ok: res.ok, error: res.error };
16621
+ }
16622
+ var import_node_crypto6, MEMORY_BASE_URL, ADMIN_KEY;
16623
+ var init_memory = __esm({
16624
+ "src/api/memory.ts"() {
16625
+ "use strict";
16626
+ import_node_crypto6 = require("crypto");
16627
+ init_session();
16628
+ init_db();
16629
+ MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://mcp-memory-omega.vercel.app").replace(/\/$/, "");
16630
+ ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
16631
+ }
16632
+ });
16633
+
16634
+ // src/api/video-routes.ts
16635
+ function invalidRequest5(message) {
16636
+ return { error_code: "invalid_request", message };
16637
+ }
16638
+ var import_hono9, import_zod20, videoApp, AnalyzeBodySchema;
16639
+ var init_video_routes = __esm({
16640
+ "src/api/video-routes.ts"() {
16641
+ "use strict";
16642
+ import_hono9 = require("hono");
16643
+ import_zod20 = require("zod");
16644
+ init_db();
16645
+ init_rates();
16646
+ init_api_auth();
16647
+ init_memory();
16648
+ videoApp = new import_hono9.Hono();
16649
+ AnalyzeBodySchema = import_zod20.z.object({
16650
+ sourceUrl: import_zod20.z.string().trim().url("sourceUrl must be a direct video file URL"),
16651
+ intervalS: import_zod20.z.number().min(1).max(30).optional(),
16652
+ maxFrames: import_zod20.z.number().int().min(1).max(120).optional(),
16653
+ detail: import_zod20.z.enum(["fast", "standard", "deep"]).optional(),
16654
+ vault: import_zod20.z.string().trim().min(1).optional()
16655
+ });
16656
+ videoApp.post("/analyze", createApiKeyAuth(), async (c) => {
16657
+ const parsed = AnalyzeBodySchema.safeParse(await c.req.json().catch(() => ({})));
16658
+ if (!parsed.success) return c.json(invalidRequest5(parsed.error.issues[0]?.message ?? "invalid request"), 400);
16659
+ const body = parsed.data;
16660
+ const user = c.get("user");
16661
+ const { key: memKey, error: memErr } = await getOrCreateUserMemoryKey(user);
16662
+ if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
16663
+ const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS, body.sourceUrl);
16664
+ if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.video_analysis), 402);
16665
+ const started = await memoryCall(
16666
+ "videoAnalyzeStartTool",
16667
+ {
16668
+ sourceUrl: body.sourceUrl,
16669
+ intervalS: body.intervalS ?? 2,
16670
+ maxFrames: body.maxFrames ?? 120,
16671
+ detail: body.detail ?? "standard",
16672
+ vault: body.vault ?? "Library"
16673
+ },
16674
+ memKey
16675
+ );
16676
+ if (!started.ok || !started.runId) {
16677
+ await creditMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS_REFUND, `start-failed:${body.sourceUrl}`);
16678
+ return c.json({ error_code: "video_start_failed", message: started.error ?? "could not start analysis" }, 502);
16679
+ }
16680
+ return c.json({
16681
+ ok: true,
16682
+ runId: started.runId,
16683
+ status: "queued",
16684
+ message: 'Analysis started. Poll GET /video/status?runId=... every few seconds until status is "done".'
16685
+ });
16686
+ });
16687
+ videoApp.post("/status", createApiKeyAuth(), async (c) => {
16688
+ const rawBody = await c.req.json().catch(() => ({}));
16689
+ const runId = rawBody.runId?.trim();
16690
+ if (!runId) return c.json(invalidRequest5("runId is required"), 400);
16691
+ const user = c.get("user");
16692
+ const { key: memKey, error: memErr } = await getOrCreateUserMemoryKey(user);
16693
+ if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
16694
+ const st = await memoryCall("videoAnalyzeStatusTool", { runId }, memKey);
16695
+ if (!st.ok) return c.json({ error_code: "not_found", message: st.error ?? "run not found" }, 404);
16696
+ if (st.status === "failed") {
16697
+ const refundOp = `${LedgerOperation.VIDEO_ANALYSIS_REFUND}:${runId}`;
16698
+ if (!await ledgerExistsForOperation(user.id, refundOp)) {
16699
+ await creditMc(user.id, MC_COSTS.video_analysis, refundOp, `run-failed:${runId}`);
16700
+ }
16701
+ }
16702
+ return c.json({
16703
+ ok: true,
16704
+ runId,
16705
+ status: st.status ?? "unknown",
16706
+ progress: st.progress ?? null,
16707
+ frameCount: st.frameCount ?? null,
16708
+ ...st.status === "done" ? { artifactPath: st.artifactPath ?? null, report: st.report ?? null } : {},
16709
+ ...st.error ? { error: st.error } : {}
16710
+ });
16711
+ });
16712
+ }
16713
+ });
16714
+
16418
16715
  // src/extractor/MapsNavigator.ts
16419
16716
  var MapsNavigator;
16420
16717
  var init_MapsNavigator = __esm({
@@ -16602,7 +16899,7 @@ var init_MapsReviewCollector = __esm({
16602
16899
  });
16603
16900
 
16604
16901
  // src/extractor/MapsExtractor.ts
16605
- var import_zod20, MapsExtractor;
16902
+ var import_zod21, MapsExtractor;
16606
16903
  var init_MapsExtractor = __esm({
16607
16904
  "src/extractor/MapsExtractor.ts"() {
16608
16905
  "use strict";
@@ -16610,7 +16907,7 @@ var init_MapsExtractor = __esm({
16610
16907
  init_selectors();
16611
16908
  init_MapsNavigator();
16612
16909
  init_MapsReviewCollector();
16613
- import_zod20 = require("zod");
16910
+ import_zod21 = require("zod");
16614
16911
  MapsExtractor = class {
16615
16912
  constructor(driver) {
16616
16913
  this.driver = driver;
@@ -16796,7 +17093,7 @@ var init_MapsExtractor = __esm({
16796
17093
  });
16797
17094
  return rows;
16798
17095
  }, { hoursTable: MapsSelectors.hoursTable, hoursTableAlt: MapsSelectors.hoursTableAlt });
16799
- const result = import_zod20.z.array(RawMapsHoursRowSchema).safeParse(raw);
17096
+ const result = import_zod21.z.array(RawMapsHoursRowSchema).safeParse(raw);
16800
17097
  if (!result.success) {
16801
17098
  console.warn("[MapsExtractor] hours parse failed", result.error.flatten());
16802
17099
  return [];
@@ -16860,7 +17157,7 @@ var init_MapsExtractor = __esm({
16860
17157
  });
16861
17158
  return results;
16862
17159
  });
16863
- const result = import_zod20.z.array(RawMapsAboutAttributeSchema).safeParse(raw);
17160
+ const result = import_zod21.z.array(RawMapsAboutAttributeSchema).safeParse(raw);
16864
17161
  if (!result.success) {
16865
17162
  console.warn("[MapsExtractor] about parse failed", result.error.flatten());
16866
17163
  return [];
@@ -17256,11 +17553,11 @@ function mapsErrorResponse(c, err, errorCode) {
17256
17553
  attempts: rotationError?.attempts ?? void 0
17257
17554
  }, retryable ? 503 : 500);
17258
17555
  }
17259
- var import_hono8, mapsApp;
17556
+ var import_hono10, mapsApp;
17260
17557
  var init_maps_routes = __esm({
17261
17558
  "src/api/maps-routes.ts"() {
17262
17559
  "use strict";
17263
- import_hono8 = require("hono");
17560
+ import_hono10 = require("hono");
17264
17561
  init_db();
17265
17562
  init_rates();
17266
17563
  init_MapsExtractor();
@@ -17271,7 +17568,7 @@ var init_maps_routes = __esm({
17271
17568
  init_browser_service_env();
17272
17569
  init_maps_search_rotation();
17273
17570
  init_concurrency_gates();
17274
- mapsApp = new import_hono8.Hono();
17571
+ mapsApp = new import_hono10.Hono();
17275
17572
  mapsApp.post("/search", createApiKeyAuth(), async (c) => {
17276
17573
  const user = c.get("user");
17277
17574
  const body = await c.req.json().catch(() => ({}));
@@ -20081,47 +20378,47 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
20081
20378
  const csvPath = options.saveCsv ? await saveDirectoryCsv(base) : null;
20082
20379
  return { ...base, csvPath };
20083
20380
  }
20084
- var import_promises6, import_node_path8, import_zod21, DirectoryWorkflowOptionsSchema;
20381
+ var import_promises6, import_node_path8, import_zod22, DirectoryWorkflowOptionsSchema;
20085
20382
  var init_directory_workflow = __esm({
20086
20383
  "src/directory/directory-workflow.ts"() {
20087
20384
  "use strict";
20088
20385
  import_promises6 = require("fs/promises");
20089
20386
  import_node_path8 = require("path");
20090
- import_zod21 = require("zod");
20387
+ import_zod22 = require("zod");
20091
20388
  init_mcp_response_formatter();
20092
20389
  init_browser_service_env();
20093
20390
  init_maps_search_rotation();
20094
20391
  init_schemas3();
20095
20392
  init_csv();
20096
20393
  init_location_db();
20097
- DirectoryWorkflowOptionsSchema = import_zod21.z.object({
20098
- query: import_zod21.z.string().min(1),
20099
- state: import_zod21.z.string().min(2).default("TN"),
20100
- minPopulation: import_zod21.z.number().int().min(0).default(1e5),
20101
- populationYear: import_zod21.z.union(POPULATION_YEARS.map((year) => import_zod21.z.literal(year))).default(2025),
20102
- maxCities: import_zod21.z.number().int().min(1).max(100).default(25),
20103
- maxResultsPerCity: import_zod21.z.number().int().min(1).max(50).default(50),
20104
- concurrency: import_zod21.z.number().int().min(1).max(5).default(5),
20105
- includeZipGroups: import_zod21.z.boolean().default(true),
20106
- usZipsCsvPath: import_zod21.z.string().optional(),
20107
- saveCsv: import_zod21.z.boolean().default(true),
20108
- gl: import_zod21.z.string().length(2).default("us"),
20109
- hl: import_zod21.z.string().length(2).default("en"),
20110
- proxyMode: import_zod21.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
20111
- proxyZip: import_zod21.z.string().regex(/^\d{5}$/).optional(),
20112
- debug: import_zod21.z.boolean().default(false),
20113
- headless: import_zod21.z.boolean().default(true),
20114
- kernelApiKey: import_zod21.z.string().optional()
20394
+ DirectoryWorkflowOptionsSchema = import_zod22.z.object({
20395
+ query: import_zod22.z.string().min(1),
20396
+ state: import_zod22.z.string().min(2).default("TN"),
20397
+ minPopulation: import_zod22.z.number().int().min(0).default(1e5),
20398
+ populationYear: import_zod22.z.union(POPULATION_YEARS.map((year) => import_zod22.z.literal(year))).default(2025),
20399
+ maxCities: import_zod22.z.number().int().min(1).max(100).default(25),
20400
+ maxResultsPerCity: import_zod22.z.number().int().min(1).max(50).default(50),
20401
+ concurrency: import_zod22.z.number().int().min(1).max(5).default(5),
20402
+ includeZipGroups: import_zod22.z.boolean().default(true),
20403
+ usZipsCsvPath: import_zod22.z.string().optional(),
20404
+ saveCsv: import_zod22.z.boolean().default(true),
20405
+ gl: import_zod22.z.string().length(2).default("us"),
20406
+ hl: import_zod22.z.string().length(2).default("en"),
20407
+ proxyMode: import_zod22.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
20408
+ proxyZip: import_zod22.z.string().regex(/^\d{5}$/).optional(),
20409
+ debug: import_zod22.z.boolean().default(false),
20410
+ headless: import_zod22.z.boolean().default(true),
20411
+ kernelApiKey: import_zod22.z.string().optional()
20115
20412
  });
20116
20413
  }
20117
20414
  });
20118
20415
 
20119
20416
  // src/api/directory-routes.ts
20120
- var import_hono9, directoryApp;
20417
+ var import_hono11, directoryApp;
20121
20418
  var init_directory_routes = __esm({
20122
20419
  "src/api/directory-routes.ts"() {
20123
20420
  "use strict";
20124
- import_hono9 = require("hono");
20421
+ import_hono11 = require("hono");
20125
20422
  init_api_auth();
20126
20423
  init_db();
20127
20424
  init_rates();
@@ -20129,7 +20426,7 @@ var init_directory_routes = __esm({
20129
20426
  init_location_db();
20130
20427
  init_browser_service_env();
20131
20428
  init_concurrency_gates();
20132
- directoryApp = new import_hono9.Hono();
20429
+ directoryApp = new import_hono11.Hono();
20133
20430
  directoryApp.post("/run", createApiKeyAuth(), async (c) => {
20134
20431
  const user = c.get("user");
20135
20432
  const body = await c.req.json().catch(() => ({}));
@@ -20537,21 +20834,21 @@ function competitorRows(rows, targetDomain) {
20537
20834
  source_url_count: entry.urls.size
20538
20835
  })).sort((a, b) => Number(a.organic_best_position || 999) - Number(b.organic_best_position || 999));
20539
20836
  }
20540
- var import_zod22, AgentPacketInputSchema, agentPacketWorkflowDefinition;
20837
+ var import_zod23, AgentPacketInputSchema, agentPacketWorkflowDefinition;
20541
20838
  var init_agent_packet = __esm({
20542
20839
  "src/workflows/workflows/agent-packet.ts"() {
20543
20840
  "use strict";
20544
- import_zod22 = require("zod");
20841
+ import_zod23 = require("zod");
20545
20842
  init_report_renderer();
20546
- AgentPacketInputSchema = import_zod22.z.object({
20547
- keyword: import_zod22.z.string().min(1),
20548
- domain: import_zod22.z.string().optional(),
20549
- location: import_zod22.z.string().optional(),
20550
- maxQuestions: import_zod22.z.number().int().min(1).max(200).default(40),
20551
- includeSerp: import_zod22.z.boolean().default(true),
20552
- includePaa: import_zod22.z.boolean().default(true),
20553
- includeAiOverview: import_zod22.z.boolean().default(true),
20554
- returnPartial: import_zod22.z.boolean().default(true)
20843
+ AgentPacketInputSchema = import_zod23.z.object({
20844
+ keyword: import_zod23.z.string().min(1),
20845
+ domain: import_zod23.z.string().optional(),
20846
+ location: import_zod23.z.string().optional(),
20847
+ maxQuestions: import_zod23.z.number().int().min(1).max(200).default(40),
20848
+ includeSerp: import_zod23.z.boolean().default(true),
20849
+ includePaa: import_zod23.z.boolean().default(true),
20850
+ includeAiOverview: import_zod23.z.boolean().default(true),
20851
+ returnPartial: import_zod23.z.boolean().default(true)
20555
20852
  });
20556
20853
  agentPacketWorkflowDefinition = {
20557
20854
  id: "agent-packet",
@@ -20749,22 +21046,22 @@ function directoryRows(result) {
20749
21046
  }
20750
21047
  return rows;
20751
21048
  }
20752
- var import_zod23, DirectoryWorkflowCliInputSchema, DIRECTORY_CSV_HEADERS, directoryWorkflowDefinition;
21049
+ var import_zod24, DirectoryWorkflowCliInputSchema, DIRECTORY_CSV_HEADERS, directoryWorkflowDefinition;
20753
21050
  var init_directory = __esm({
20754
21051
  "src/workflows/workflows/directory.ts"() {
20755
21052
  "use strict";
20756
- import_zod23 = require("zod");
21053
+ import_zod24 = require("zod");
20757
21054
  init_report_renderer();
20758
21055
  init_schemas3();
20759
- DirectoryWorkflowCliInputSchema = import_zod23.z.object({
20760
- query: import_zod23.z.string().min(1),
20761
- state: import_zod23.z.string().min(2).default("TN"),
20762
- minPopulation: import_zod23.z.number().int().min(0).default(1e5),
20763
- maxCities: import_zod23.z.number().int().min(1).max(100).default(25),
20764
- maxResultsPerCity: import_zod23.z.number().int().min(1).max(50).default(20),
20765
- concurrency: import_zod23.z.number().int().min(1).max(5).default(5),
20766
- proxyMode: import_zod23.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
20767
- saveCsv: import_zod23.z.boolean().default(true)
21056
+ DirectoryWorkflowCliInputSchema = import_zod24.z.object({
21057
+ query: import_zod24.z.string().min(1),
21058
+ state: import_zod24.z.string().min(2).default("TN"),
21059
+ minPopulation: import_zod24.z.number().int().min(0).default(1e5),
21060
+ maxCities: import_zod24.z.number().int().min(1).max(100).default(25),
21061
+ maxResultsPerCity: import_zod24.z.number().int().min(1).max(50).default(20),
21062
+ concurrency: import_zod24.z.number().int().min(1).max(5).default(5),
21063
+ proxyMode: import_zod24.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
21064
+ saveCsv: import_zod24.z.boolean().default(true)
20768
21065
  });
20769
21066
  DIRECTORY_CSV_HEADERS = [
20770
21067
  "source_query",
@@ -20904,21 +21201,21 @@ function buildAttemptUrls(website) {
20904
21201
  }
20905
21202
  return [...urls].slice(0, 3);
20906
21203
  }
20907
- var import_zod24, GetLeadsInputSchema, LEADS_CSV_HEADERS, EMAIL_RE, MAILTO_RE, SOCIAL_RE, EMAIL_BAD, EMAIL_PREFIX, SOCIAL_SKIP, getLeadsWorkflowDefinition;
21204
+ var import_zod25, GetLeadsInputSchema, LEADS_CSV_HEADERS, EMAIL_RE, MAILTO_RE, SOCIAL_RE, EMAIL_BAD, EMAIL_PREFIX, SOCIAL_SKIP, getLeadsWorkflowDefinition;
20908
21205
  var init_get_leads = __esm({
20909
21206
  "src/workflows/workflows/get-leads.ts"() {
20910
21207
  "use strict";
20911
- import_zod24 = require("zod");
21208
+ import_zod25 = require("zod");
20912
21209
  init_report_renderer();
20913
21210
  init_schemas3();
20914
- GetLeadsInputSchema = import_zod24.z.object({
20915
- query: import_zod24.z.string().min(1).describe('Business category, niche, or keyword to search on Google Maps, e.g. "roofers", "med spas", "dentists". Do not include the city here.'),
20916
- location: import_zod24.z.string().min(1).describe('City / market to search, e.g. "Houston, TX" or "Austin, Texas".'),
20917
- maxResults: import_zod24.z.number().int().min(1).max(50).default(25).describe("How many Maps businesses to collect for the market. Maximum 50."),
20918
- enrichWebsites: import_zod24.z.boolean().default(true).describe("Visit each business website (home + contact pages) to harvest email and social links. Uses the proxy/browser-backed extractor so blocked sites still resolve."),
20919
- hydrateReviewCounts: import_zod24.z.boolean().default(true).describe("Deep-dive each profile to confirm the review count and booking URL that the Maps search list omits."),
20920
- concurrency: import_zod24.z.number().int().min(1).max(4).default(3).describe("How many businesses to enrich in parallel. Keep low to respect per-account concurrency limits."),
20921
- proxyMode: import_zod24.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting for the Maps search. location targets the market; none is for local debugging.")
21211
+ GetLeadsInputSchema = import_zod25.z.object({
21212
+ query: import_zod25.z.string().min(1).describe('Business category, niche, or keyword to search on Google Maps, e.g. "roofers", "med spas", "dentists". Do not include the city here.'),
21213
+ location: import_zod25.z.string().min(1).describe('City / market to search, e.g. "Houston, TX" or "Austin, Texas".'),
21214
+ maxResults: import_zod25.z.number().int().min(1).max(50).default(25).describe("How many Maps businesses to collect for the market. Maximum 50."),
21215
+ enrichWebsites: import_zod25.z.boolean().default(true).describe("Visit each business website (home + contact pages) to harvest email and social links. Uses the proxy/browser-backed extractor so blocked sites still resolve."),
21216
+ hydrateReviewCounts: import_zod25.z.boolean().default(true).describe("Deep-dive each profile to confirm the review count and booking URL that the Maps search list omits."),
21217
+ concurrency: import_zod25.z.number().int().min(1).max(4).default(3).describe("How many businesses to enrich in parallel. Keep low to respect per-account concurrency limits."),
21218
+ proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting for the Maps search. location targets the market; none is for local debugging.")
20922
21219
  });
20923
21220
  LEADS_CSV_HEADERS = [
20924
21221
  "position",
@@ -21120,25 +21417,25 @@ function termsFrom(texts) {
21120
21417
  }
21121
21418
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([term, count]) => `${term} (${count})`).join("; ");
21122
21419
  }
21123
- var import_zod25, LocalCompetitiveAuditInputSchema, localCompetitiveAuditWorkflowDefinition;
21420
+ var import_zod26, LocalCompetitiveAuditInputSchema, localCompetitiveAuditWorkflowDefinition;
21124
21421
  var init_local_competitive_audit = __esm({
21125
21422
  "src/workflows/workflows/local-competitive-audit.ts"() {
21126
21423
  "use strict";
21127
- import_zod25 = require("zod");
21424
+ import_zod26 = require("zod");
21128
21425
  init_report_renderer();
21129
21426
  init_directory();
21130
21427
  init_schemas3();
21131
- LocalCompetitiveAuditInputSchema = import_zod25.z.object({
21132
- query: import_zod25.z.string().min(1),
21133
- state: import_zod25.z.string().min(2).default("TN"),
21134
- minPopulation: import_zod25.z.number().int().min(0).default(1e5),
21135
- maxCities: import_zod25.z.number().int().min(1).max(100).default(25),
21136
- maxResultsPerCity: import_zod25.z.number().int().min(1).max(50).default(20),
21137
- hydrateTop: import_zod25.z.number().int().min(0).max(10).default(5),
21138
- maxReviews: import_zod25.z.number().int().min(0).max(500).default(50),
21139
- concurrency: import_zod25.z.number().int().min(1).max(5).default(5),
21140
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
21141
- returnPartial: import_zod25.z.boolean().default(true)
21428
+ LocalCompetitiveAuditInputSchema = import_zod26.z.object({
21429
+ query: import_zod26.z.string().min(1),
21430
+ state: import_zod26.z.string().min(2).default("TN"),
21431
+ minPopulation: import_zod26.z.number().int().min(0).default(1e5),
21432
+ maxCities: import_zod26.z.number().int().min(1).max(100).default(25),
21433
+ maxResultsPerCity: import_zod26.z.number().int().min(1).max(50).default(20),
21434
+ hydrateTop: import_zod26.z.number().int().min(0).max(10).default(5),
21435
+ maxReviews: import_zod26.z.number().int().min(0).max(500).default(50),
21436
+ concurrency: import_zod26.z.number().int().min(1).max(5).default(5),
21437
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
21438
+ returnPartial: import_zod26.z.boolean().default(true)
21142
21439
  });
21143
21440
  localCompetitiveAuditWorkflowDefinition = {
21144
21441
  id: "local-competitive-audit",
@@ -21581,58 +21878,58 @@ async function extractPages(ctx, sources, warnings, labelPrefix) {
21581
21878
  }
21582
21879
  }).then((items) => items.filter((item) => item !== null));
21583
21880
  }
21584
- var import_zod26, ProxyModeSchema, MapComparisonInputSchema, SerpComparisonInputSchema, PaaExpansionBriefInputSchema, AiOverviewLanguageInputSchema, mapComparisonWorkflowDefinition, serpComparisonWorkflowDefinition, paaExpansionBriefWorkflowDefinition, aiOverviewLanguageWorkflowDefinition;
21881
+ var import_zod27, ProxyModeSchema, MapComparisonInputSchema, SerpComparisonInputSchema, PaaExpansionBriefInputSchema, AiOverviewLanguageInputSchema, mapComparisonWorkflowDefinition, serpComparisonWorkflowDefinition, paaExpansionBriefWorkflowDefinition, aiOverviewLanguageWorkflowDefinition;
21585
21882
  var init_comparison_briefs = __esm({
21586
21883
  "src/workflows/workflows/comparison-briefs.ts"() {
21587
21884
  "use strict";
21588
- import_zod26 = require("zod");
21885
+ import_zod27 = require("zod");
21589
21886
  init_report_renderer();
21590
21887
  init_directory();
21591
21888
  init_seo_workflow_utils();
21592
21889
  init_schemas3();
21593
- ProxyModeSchema = import_zod26.z.enum(["location", "configured", "none"]);
21594
- MapComparisonInputSchema = import_zod26.z.object({
21595
- query: import_zod26.z.string().min(1),
21596
- location: import_zod26.z.string().optional(),
21597
- state: import_zod26.z.string().optional(),
21598
- minPopulation: import_zod26.z.number().int().min(0).default(1e5),
21599
- maxCities: import_zod26.z.number().int().min(1).max(100).default(5),
21600
- maxResultsPerCity: import_zod26.z.number().int().min(1).max(50).default(20),
21601
- hydrateTop: import_zod26.z.number().int().min(0).max(10).default(5),
21602
- maxReviews: import_zod26.z.number().int().min(0).max(500).default(25),
21603
- concurrency: import_zod26.z.number().int().min(1).max(5).default(5),
21890
+ ProxyModeSchema = import_zod27.z.enum(["location", "configured", "none"]);
21891
+ MapComparisonInputSchema = import_zod27.z.object({
21892
+ query: import_zod27.z.string().min(1),
21893
+ location: import_zod27.z.string().optional(),
21894
+ state: import_zod27.z.string().optional(),
21895
+ minPopulation: import_zod27.z.number().int().min(0).default(1e5),
21896
+ maxCities: import_zod27.z.number().int().min(1).max(100).default(5),
21897
+ maxResultsPerCity: import_zod27.z.number().int().min(1).max(50).default(20),
21898
+ hydrateTop: import_zod27.z.number().int().min(0).max(10).default(5),
21899
+ maxReviews: import_zod27.z.number().int().min(0).max(500).default(25),
21900
+ concurrency: import_zod27.z.number().int().min(1).max(5).default(5),
21604
21901
  proxyMode: ProxyModeSchema.default(DEFAULT_MAPS_PROXY_MODE),
21605
- returnPartial: import_zod26.z.boolean().default(true)
21902
+ returnPartial: import_zod27.z.boolean().default(true)
21606
21903
  }).refine((input) => input.location || input.state, {
21607
21904
  message: "Either location or state is required for map-comparison"
21608
21905
  });
21609
- SerpComparisonInputSchema = import_zod26.z.object({
21610
- keyword: import_zod26.z.string().min(1),
21611
- domain: import_zod26.z.string().optional(),
21612
- url: import_zod26.z.string().url().optional(),
21613
- location: import_zod26.z.string().optional(),
21614
- maxResults: import_zod26.z.number().int().min(1).max(20).default(10),
21615
- maxQuestions: import_zod26.z.number().int().min(1).max(200).default(40),
21616
- extractTop: import_zod26.z.number().int().min(0).max(10).default(5),
21617
- includePaa: import_zod26.z.boolean().default(true),
21618
- includeAiOverview: import_zod26.z.boolean().default(true),
21619
- returnPartial: import_zod26.z.boolean().default(true)
21620
- });
21621
- PaaExpansionBriefInputSchema = import_zod26.z.object({
21622
- keyword: import_zod26.z.string().min(1),
21623
- location: import_zod26.z.string().optional(),
21624
- maxQuestions: import_zod26.z.number().int().min(1).max(300).default(80),
21625
- depth: import_zod26.z.number().int().min(1).max(6).default(3),
21626
- returnPartial: import_zod26.z.boolean().default(true)
21627
- });
21628
- AiOverviewLanguageInputSchema = import_zod26.z.object({
21629
- keyword: import_zod26.z.string().min(1),
21630
- domain: import_zod26.z.string().optional(),
21631
- url: import_zod26.z.string().url().optional(),
21632
- location: import_zod26.z.string().optional(),
21633
- maxQuestions: import_zod26.z.number().int().min(1).max(200).default(40),
21634
- extractTop: import_zod26.z.number().int().min(0).max(8).default(3),
21635
- returnPartial: import_zod26.z.boolean().default(true)
21906
+ SerpComparisonInputSchema = import_zod27.z.object({
21907
+ keyword: import_zod27.z.string().min(1),
21908
+ domain: import_zod27.z.string().optional(),
21909
+ url: import_zod27.z.string().url().optional(),
21910
+ location: import_zod27.z.string().optional(),
21911
+ maxResults: import_zod27.z.number().int().min(1).max(20).default(10),
21912
+ maxQuestions: import_zod27.z.number().int().min(1).max(200).default(40),
21913
+ extractTop: import_zod27.z.number().int().min(0).max(10).default(5),
21914
+ includePaa: import_zod27.z.boolean().default(true),
21915
+ includeAiOverview: import_zod27.z.boolean().default(true),
21916
+ returnPartial: import_zod27.z.boolean().default(true)
21917
+ });
21918
+ PaaExpansionBriefInputSchema = import_zod27.z.object({
21919
+ keyword: import_zod27.z.string().min(1),
21920
+ location: import_zod27.z.string().optional(),
21921
+ maxQuestions: import_zod27.z.number().int().min(1).max(300).default(80),
21922
+ depth: import_zod27.z.number().int().min(1).max(6).default(3),
21923
+ returnPartial: import_zod27.z.boolean().default(true)
21924
+ });
21925
+ AiOverviewLanguageInputSchema = import_zod27.z.object({
21926
+ keyword: import_zod27.z.string().min(1),
21927
+ domain: import_zod27.z.string().optional(),
21928
+ url: import_zod27.z.string().url().optional(),
21929
+ location: import_zod27.z.string().optional(),
21930
+ maxQuestions: import_zod27.z.number().int().min(1).max(200).default(40),
21931
+ extractTop: import_zod27.z.number().int().min(0).max(8).default(3),
21932
+ returnPartial: import_zod27.z.boolean().default(true)
21636
21933
  });
21637
21934
  mapComparisonWorkflowDefinition = {
21638
21935
  id: "map-comparison",
@@ -22102,12 +22399,12 @@ async function runWorkflowStep(id, rawInput, opts) {
22102
22399
  artifacts: writtenArtifacts
22103
22400
  };
22104
22401
  }
22105
- var import_promises8, import_zod27, DEFINITIONS;
22402
+ var import_promises8, import_zod28, DEFINITIONS;
22106
22403
  var init_registry2 = __esm({
22107
22404
  "src/workflows/registry.ts"() {
22108
22405
  "use strict";
22109
22406
  import_promises8 = require("fs/promises");
22110
- import_zod27 = require("zod");
22407
+ import_zod28 = require("zod");
22111
22408
  init_artifact_writer();
22112
22409
  init_http_client2();
22113
22410
  init_agent_packet();
@@ -22185,7 +22482,7 @@ async function readManifestFromSummary(summary) {
22185
22482
  function webhookSignature(body, timestamp2) {
22186
22483
  const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
22187
22484
  if (!secret2) return null;
22188
- return (0, import_node_crypto5.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
22485
+ return (0, import_node_crypto7.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
22189
22486
  }
22190
22487
  async function deliverWorkflowWebhook(input) {
22191
22488
  if (!input.webhookUrl) return;
@@ -22392,47 +22689,47 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
22392
22689
  }
22393
22690
  return { dispatched: results.length, results };
22394
22691
  }
22395
- var import_node_crypto5, import_promises9, import_hono10, import_zod28, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
22692
+ var import_node_crypto7, import_promises9, import_hono12, import_zod29, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
22396
22693
  var init_workflow_routes = __esm({
22397
22694
  "src/api/workflow-routes.ts"() {
22398
22695
  "use strict";
22399
- import_node_crypto5 = require("crypto");
22696
+ import_node_crypto7 = require("crypto");
22400
22697
  import_promises9 = require("fs/promises");
22401
- import_hono10 = require("hono");
22402
- import_zod28 = require("zod");
22403
- init_artifact_writer();
22698
+ import_hono12 = require("hono");
22699
+ import_zod29 = require("zod");
22700
+ init_artifact_writer();
22404
22701
  init_registry2();
22405
22702
  init_api_auth();
22406
22703
  init_db();
22407
22704
  init_url_utils();
22408
22705
  init_concurrency_gates();
22409
- workflowApp = new import_hono10.Hono();
22410
- WorkflowInputSchema = import_zod28.z.record(import_zod28.z.unknown()).default({});
22411
- WorkflowIdSchema = import_zod28.z.string().min(1);
22412
- CadenceSchema = import_zod28.z.enum(["daily", "weekly", "monthly"]);
22413
- ScheduleStatusSchema = import_zod28.z.enum(["active", "paused"]);
22414
- RunBodySchema = import_zod28.z.object({
22706
+ workflowApp = new import_hono12.Hono();
22707
+ WorkflowInputSchema = import_zod29.z.record(import_zod29.z.unknown()).default({});
22708
+ WorkflowIdSchema = import_zod29.z.string().min(1);
22709
+ CadenceSchema = import_zod29.z.enum(["daily", "weekly", "monthly"]);
22710
+ ScheduleStatusSchema = import_zod29.z.enum(["active", "paused"]);
22711
+ RunBodySchema = import_zod29.z.object({
22415
22712
  workflowId: WorkflowIdSchema,
22416
22713
  input: WorkflowInputSchema,
22417
- webhookUrl: import_zod28.z.string().url().optional()
22714
+ webhookUrl: import_zod29.z.string().url().optional()
22418
22715
  });
22419
- ScheduleCreateSchema = import_zod28.z.object({
22716
+ ScheduleCreateSchema = import_zod29.z.object({
22420
22717
  workflowId: WorkflowIdSchema,
22421
- name: import_zod28.z.string().min(1).max(120).optional(),
22718
+ name: import_zod29.z.string().min(1).max(120).optional(),
22422
22719
  input: WorkflowInputSchema,
22423
22720
  cadence: CadenceSchema.default("weekly"),
22424
- timezone: import_zod28.z.string().min(1).max(64).default("UTC"),
22425
- webhookUrl: import_zod28.z.string().url().optional(),
22426
- nextRunAt: import_zod28.z.string().datetime().optional()
22721
+ timezone: import_zod29.z.string().min(1).max(64).default("UTC"),
22722
+ webhookUrl: import_zod29.z.string().url().optional(),
22723
+ nextRunAt: import_zod29.z.string().datetime().optional()
22427
22724
  });
22428
- SchedulePatchSchema = import_zod28.z.object({
22429
- name: import_zod28.z.string().min(1).max(120).optional(),
22725
+ SchedulePatchSchema = import_zod29.z.object({
22726
+ name: import_zod29.z.string().min(1).max(120).optional(),
22430
22727
  status: ScheduleStatusSchema.optional(),
22431
22728
  input: WorkflowInputSchema.optional(),
22432
22729
  cadence: CadenceSchema.optional(),
22433
- timezone: import_zod28.z.string().min(1).max(64).optional(),
22434
- webhookUrl: import_zod28.z.string().url().nullable().optional(),
22435
- nextRunAt: import_zod28.z.string().datetime().nullable().optional()
22730
+ timezone: import_zod29.z.string().min(1).max(64).optional(),
22731
+ webhookUrl: import_zod29.z.string().url().nullable().optional(),
22732
+ nextRunAt: import_zod29.z.string().datetime().nullable().optional()
22436
22733
  });
22437
22734
  workflowApp.get("/definitions", createApiKeyAuth(), (c) => {
22438
22735
  return c.json({ workflows: listWorkflowDefinitions() });
@@ -22677,7 +22974,7 @@ var init_workflow_routes = __esm({
22677
22974
  // src/serp-intelligence/page-snapshot-extractor.ts
22678
22975
  function sha256(value) {
22679
22976
  if (!value) return null;
22680
- return (0, import_node_crypto6.createHash)("sha256").update(value).digest("hex");
22977
+ return (0, import_node_crypto8.createHash)("sha256").update(value).digest("hex");
22681
22978
  }
22682
22979
  function countWords(markdown) {
22683
22980
  const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
@@ -22977,11 +23274,11 @@ async function capturePageSnapshots(targets, options = {}) {
22977
23274
  }
22978
23275
  };
22979
23276
  }
22980
- var import_node_crypto6, import_p_limit3, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
23277
+ var import_node_crypto8, import_p_limit3, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
22981
23278
  var init_page_snapshot_extractor = __esm({
22982
23279
  "src/serp-intelligence/page-snapshot-extractor.ts"() {
22983
23280
  "use strict";
22984
- import_node_crypto6 = require("crypto");
23281
+ import_node_crypto8 = require("crypto");
22985
23282
  import_p_limit3 = __toESM(require("p-limit"), 1);
22986
23283
  init_kpo_extractor();
22987
23284
  init_url_utils();
@@ -24981,11 +25278,11 @@ function isPublicHttpUrl(value) {
24981
25278
  return false;
24982
25279
  }
24983
25280
  }
24984
- var import_zod29, SerpIntelligenceDeviceValues, SerpIntelligenceProxyModeValues, SerpIntelligenceAttemptOutcomeValues, SerpIntelligenceLocalizationStatusValues, SerpPageSnapshotSourceKindValues, SerpPageFetchStatusValues, SerpPageFetchedViaValues, HostnameSuffixPattern, Ipv4Pattern, SerpIntelligencePublicHttpUrlSchema, SerpIntelligenceCaptureBodySchema, SerpIntelligencePageSnapshotRequestSchema, SerpIntelligencePageSnapshotsBodySchema, SerpIntelligenceAICitationSchema, SerpIntelligenceOrganicResultSchema, SerpIntelligenceLocationEvidenceSchema, SerpIntelligenceHarvestResultSchema, SerpIntelligenceCaptureAttemptSchema, SerpPageSnapshotCaptureSchema, SerpIntelligenceCaptureResponseSchema, SerpIntelligencePageSnapshotsResponseSchema;
25281
+ var import_zod30, SerpIntelligenceDeviceValues, SerpIntelligenceProxyModeValues, SerpIntelligenceAttemptOutcomeValues, SerpIntelligenceLocalizationStatusValues, SerpPageSnapshotSourceKindValues, SerpPageFetchStatusValues, SerpPageFetchedViaValues, HostnameSuffixPattern, Ipv4Pattern, SerpIntelligencePublicHttpUrlSchema, SerpIntelligenceCaptureBodySchema, SerpIntelligencePageSnapshotRequestSchema, SerpIntelligencePageSnapshotsBodySchema, SerpIntelligenceAICitationSchema, SerpIntelligenceOrganicResultSchema, SerpIntelligenceLocationEvidenceSchema, SerpIntelligenceHarvestResultSchema, SerpIntelligenceCaptureAttemptSchema, SerpPageSnapshotCaptureSchema, SerpIntelligenceCaptureResponseSchema, SerpIntelligencePageSnapshotsResponseSchema;
24985
25282
  var init_schemas4 = __esm({
24986
25283
  "src/serp-intelligence/schemas.ts"() {
24987
25284
  "use strict";
24988
- import_zod29 = require("zod");
25285
+ import_zod30 = require("zod");
24989
25286
  init_schemas3();
24990
25287
  SerpIntelligenceDeviceValues = ["desktop", "mobile"];
24991
25288
  SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
@@ -25020,171 +25317,171 @@ var init_schemas4 = __esm({
25020
25317
  SerpPageFetchedViaValues = ["fetch", "headless", "browser", "mcp"];
25021
25318
  HostnameSuffixPattern = /(^|\.)localhost$/i;
25022
25319
  Ipv4Pattern = /^\d{1,3}(?:\.\d{1,3}){3}$/;
25023
- SerpIntelligencePublicHttpUrlSchema = import_zod29.z.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
25024
- SerpIntelligenceCaptureBodySchema = import_zod29.z.object({
25025
- query: import_zod29.z.string().trim().min(1, "query is required"),
25026
- location: import_zod29.z.string().trim().min(1).optional(),
25027
- gl: import_zod29.z.string().trim().length(2).default("us"),
25028
- hl: import_zod29.z.string().trim().length(2).default("en"),
25029
- device: import_zod29.z.enum(SerpIntelligenceDeviceValues).default("desktop"),
25030
- proxyMode: import_zod29.z.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
25031
- proxyZip: import_zod29.z.string().regex(/^\d{5}$/).optional(),
25032
- pages: import_zod29.z.number().int().min(1).max(2).default(1),
25033
- debug: import_zod29.z.boolean().default(false),
25034
- includePageSnapshots: import_zod29.z.boolean().default(false),
25035
- pageSnapshotLimit: import_zod29.z.number().int().min(0).max(10).default(0)
25320
+ SerpIntelligencePublicHttpUrlSchema = import_zod30.z.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
25321
+ SerpIntelligenceCaptureBodySchema = import_zod30.z.object({
25322
+ query: import_zod30.z.string().trim().min(1, "query is required"),
25323
+ location: import_zod30.z.string().trim().min(1).optional(),
25324
+ gl: import_zod30.z.string().trim().length(2).default("us"),
25325
+ hl: import_zod30.z.string().trim().length(2).default("en"),
25326
+ device: import_zod30.z.enum(SerpIntelligenceDeviceValues).default("desktop"),
25327
+ proxyMode: import_zod30.z.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
25328
+ proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional(),
25329
+ pages: import_zod30.z.number().int().min(1).max(2).default(1),
25330
+ debug: import_zod30.z.boolean().default(false),
25331
+ includePageSnapshots: import_zod30.z.boolean().default(false),
25332
+ pageSnapshotLimit: import_zod30.z.number().int().min(0).max(10).default(0)
25036
25333
  }).strict();
25037
- SerpIntelligencePageSnapshotRequestSchema = import_zod29.z.object({
25334
+ SerpIntelligencePageSnapshotRequestSchema = import_zod30.z.object({
25038
25335
  url: SerpIntelligencePublicHttpUrlSchema,
25039
- sourceKind: import_zod29.z.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
25040
- sourcePosition: import_zod29.z.number().int().min(1).optional()
25336
+ sourceKind: import_zod30.z.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
25337
+ sourcePosition: import_zod30.z.number().int().min(1).optional()
25041
25338
  }).strict();
25042
- SerpIntelligencePageSnapshotsBodySchema = import_zod29.z.object({
25043
- urls: import_zod29.z.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
25044
- targets: import_zod29.z.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
25045
- maxConcurrency: import_zod29.z.number().int().min(1).max(5).default(2),
25046
- timeoutMs: import_zod29.z.number().int().min(1e3).max(6e4).default(15e3),
25047
- debug: import_zod29.z.boolean().default(false)
25339
+ SerpIntelligencePageSnapshotsBodySchema = import_zod30.z.object({
25340
+ urls: import_zod30.z.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
25341
+ targets: import_zod30.z.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
25342
+ maxConcurrency: import_zod30.z.number().int().min(1).max(5).default(2),
25343
+ timeoutMs: import_zod30.z.number().int().min(1e3).max(6e4).default(15e3),
25344
+ debug: import_zod30.z.boolean().default(false)
25048
25345
  }).strict();
25049
- SerpIntelligenceAICitationSchema = import_zod29.z.object({
25050
- text: import_zod29.z.string(),
25051
- href: import_zod29.z.string()
25346
+ SerpIntelligenceAICitationSchema = import_zod30.z.object({
25347
+ text: import_zod30.z.string(),
25348
+ href: import_zod30.z.string()
25052
25349
  }).strict();
25053
- SerpIntelligenceOrganicResultSchema = import_zod29.z.object({
25054
- position: import_zod29.z.number().int().min(1),
25055
- title: import_zod29.z.string(),
25056
- url: import_zod29.z.string(),
25057
- domain: import_zod29.z.string(),
25058
- cite: import_zod29.z.string().nullable(),
25059
- snippet: import_zod29.z.string().nullable(),
25060
- isRedditStyle: import_zod29.z.boolean(),
25061
- inlineRating: import_zod29.z.object({
25062
- value: import_zod29.z.string(),
25063
- count: import_zod29.z.string()
25350
+ SerpIntelligenceOrganicResultSchema = import_zod30.z.object({
25351
+ position: import_zod30.z.number().int().min(1),
25352
+ title: import_zod30.z.string(),
25353
+ url: import_zod30.z.string(),
25354
+ domain: import_zod30.z.string(),
25355
+ cite: import_zod30.z.string().nullable(),
25356
+ snippet: import_zod30.z.string().nullable(),
25357
+ isRedditStyle: import_zod30.z.boolean(),
25358
+ inlineRating: import_zod30.z.object({
25359
+ value: import_zod30.z.string(),
25360
+ count: import_zod30.z.string()
25064
25361
  }).strict().nullable()
25065
25362
  }).strict();
25066
- SerpIntelligenceLocationEvidenceSchema = import_zod29.z.object({
25067
- status: import_zod29.z.enum(SerpIntelligenceLocalizationStatusValues),
25068
- expected: import_zod29.z.object({
25069
- city: import_zod29.z.string(),
25070
- regionCode: import_zod29.z.string().nullable(),
25071
- canonicalLocation: import_zod29.z.string()
25363
+ SerpIntelligenceLocationEvidenceSchema = import_zod30.z.object({
25364
+ status: import_zod30.z.enum(SerpIntelligenceLocalizationStatusValues),
25365
+ expected: import_zod30.z.object({
25366
+ city: import_zod30.z.string(),
25367
+ regionCode: import_zod30.z.string().nullable(),
25368
+ canonicalLocation: import_zod30.z.string()
25072
25369
  }).strict().nullable(),
25073
- candidates: import_zod29.z.array(import_zod29.z.object({
25074
- city: import_zod29.z.string(),
25075
- regionCode: import_zod29.z.string(),
25076
- count: import_zod29.z.number().int().min(0),
25077
- examples: import_zod29.z.array(import_zod29.z.string())
25370
+ candidates: import_zod30.z.array(import_zod30.z.object({
25371
+ city: import_zod30.z.string(),
25372
+ regionCode: import_zod30.z.string(),
25373
+ count: import_zod30.z.number().int().min(0),
25374
+ examples: import_zod30.z.array(import_zod30.z.string())
25078
25375
  }).strict())
25079
25376
  }).strict();
25080
- SerpIntelligenceHarvestResultSchema = import_zod29.z.object({
25081
- seed: import_zod29.z.string(),
25082
- location: import_zod29.z.string().nullable(),
25083
- extractedAt: import_zod29.z.string(),
25084
- totalQuestions: import_zod29.z.number().int().min(0),
25085
- surface: import_zod29.z.enum(["web", "aim", "unknown"]),
25086
- aiOverview: import_zod29.z.object({
25087
- detected: import_zod29.z.boolean(),
25088
- text: import_zod29.z.string().nullable(),
25089
- citations: import_zod29.z.array(SerpIntelligenceAICitationSchema),
25090
- expanded: import_zod29.z.boolean().optional(),
25091
- fullyExpanded: import_zod29.z.boolean().optional(),
25092
- sections: import_zod29.z.array(import_zod29.z.string()).optional()
25377
+ SerpIntelligenceHarvestResultSchema = import_zod30.z.object({
25378
+ seed: import_zod30.z.string(),
25379
+ location: import_zod30.z.string().nullable(),
25380
+ extractedAt: import_zod30.z.string(),
25381
+ totalQuestions: import_zod30.z.number().int().min(0),
25382
+ surface: import_zod30.z.enum(["web", "aim", "unknown"]),
25383
+ aiOverview: import_zod30.z.object({
25384
+ detected: import_zod30.z.boolean(),
25385
+ text: import_zod30.z.string().nullable(),
25386
+ citations: import_zod30.z.array(SerpIntelligenceAICitationSchema),
25387
+ expanded: import_zod30.z.boolean().optional(),
25388
+ fullyExpanded: import_zod30.z.boolean().optional(),
25389
+ sections: import_zod30.z.array(import_zod30.z.string()).optional()
25093
25390
  }).strict(),
25094
- aiMode: import_zod29.z.object({
25095
- detected: import_zod29.z.boolean(),
25096
- text: import_zod29.z.string().nullable(),
25097
- citations: import_zod29.z.array(SerpIntelligenceAICitationSchema)
25391
+ aiMode: import_zod30.z.object({
25392
+ detected: import_zod30.z.boolean(),
25393
+ text: import_zod30.z.string().nullable(),
25394
+ citations: import_zod30.z.array(SerpIntelligenceAICitationSchema)
25098
25395
  }).strict(),
25099
- tree: import_zod29.z.array(import_zod29.z.unknown()),
25100
- flat: import_zod29.z.array(import_zod29.z.unknown()),
25101
- videos: import_zod29.z.array(import_zod29.z.unknown()),
25102
- forums: import_zod29.z.array(import_zod29.z.unknown()),
25103
- organicResults: import_zod29.z.array(SerpIntelligenceOrganicResultSchema),
25104
- localPack: import_zod29.z.array(import_zod29.z.unknown()),
25105
- entityIds: import_zod29.z.object({
25106
- entities: import_zod29.z.array(import_zod29.z.object({
25107
- name: import_zod29.z.string(),
25108
- kgId: import_zod29.z.string().nullable(),
25109
- cid: import_zod29.z.string().nullable(),
25110
- gcid: import_zod29.z.string().nullable()
25396
+ tree: import_zod30.z.array(import_zod30.z.unknown()),
25397
+ flat: import_zod30.z.array(import_zod30.z.unknown()),
25398
+ videos: import_zod30.z.array(import_zod30.z.unknown()),
25399
+ forums: import_zod30.z.array(import_zod30.z.unknown()),
25400
+ organicResults: import_zod30.z.array(SerpIntelligenceOrganicResultSchema),
25401
+ localPack: import_zod30.z.array(import_zod30.z.unknown()),
25402
+ entityIds: import_zod30.z.object({
25403
+ entities: import_zod30.z.array(import_zod30.z.object({
25404
+ name: import_zod30.z.string(),
25405
+ kgId: import_zod30.z.string().nullable(),
25406
+ cid: import_zod30.z.string().nullable(),
25407
+ gcid: import_zod30.z.string().nullable()
25111
25408
  }).strict()),
25112
- kgIds: import_zod29.z.array(import_zod29.z.string()),
25113
- cids: import_zod29.z.array(import_zod29.z.string()),
25114
- gcids: import_zod29.z.array(import_zod29.z.string())
25409
+ kgIds: import_zod30.z.array(import_zod30.z.string()),
25410
+ cids: import_zod30.z.array(import_zod30.z.string()),
25411
+ gcids: import_zod30.z.array(import_zod30.z.string())
25115
25412
  }).strict(),
25116
- stats: import_zod29.z.object({
25117
- seed: import_zod29.z.string(),
25118
- totalQuestions: import_zod29.z.number().int().min(0),
25119
- maxDepthReached: import_zod29.z.number().int().min(0),
25120
- durationMs: import_zod29.z.number().min(0),
25121
- errorCount: import_zod29.z.number().int().min(0)
25413
+ stats: import_zod30.z.object({
25414
+ seed: import_zod30.z.string(),
25415
+ totalQuestions: import_zod30.z.number().int().min(0),
25416
+ maxDepthReached: import_zod30.z.number().int().min(0),
25417
+ durationMs: import_zod30.z.number().min(0),
25418
+ errorCount: import_zod30.z.number().int().min(0)
25122
25419
  }).strict(),
25123
- diagnostics: import_zod29.z.object({
25124
- completionStatus: import_zod29.z.enum(["paa_found", "no_paa", "serp_only"]),
25125
- problem: import_zod29.z.null(),
25126
- warnings: import_zod29.z.array(import_zod29.z.unknown()).optional(),
25127
- debug: import_zod29.z.object({
25420
+ diagnostics: import_zod30.z.object({
25421
+ completionStatus: import_zod30.z.enum(["paa_found", "no_paa", "serp_only"]),
25422
+ problem: import_zod30.z.null(),
25423
+ warnings: import_zod30.z.array(import_zod30.z.unknown()).optional(),
25424
+ debug: import_zod30.z.object({
25128
25425
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
25129
25426
  }).passthrough().optional()
25130
25427
  }).passthrough(),
25131
- whatPeopleSaying: import_zod29.z.array(import_zod29.z.unknown())
25428
+ whatPeopleSaying: import_zod30.z.array(import_zod30.z.unknown())
25132
25429
  }).strict();
25133
- SerpIntelligenceCaptureAttemptSchema = import_zod29.z.object({
25134
- attemptNumber: import_zod29.z.number().int().min(1),
25135
- outcome: import_zod29.z.enum(SerpIntelligenceAttemptOutcomeValues),
25136
- startedAt: import_zod29.z.string().optional(),
25137
- completedAt: import_zod29.z.string().optional(),
25138
- durationMs: import_zod29.z.number().min(0).optional(),
25139
- problemCode: import_zod29.z.string().optional(),
25140
- message: import_zod29.z.string().optional(),
25141
- kernelSessionId: import_zod29.z.string().nullable().optional(),
25142
- cleanupSucceeded: import_zod29.z.boolean().nullable().optional()
25430
+ SerpIntelligenceCaptureAttemptSchema = import_zod30.z.object({
25431
+ attemptNumber: import_zod30.z.number().int().min(1),
25432
+ outcome: import_zod30.z.enum(SerpIntelligenceAttemptOutcomeValues),
25433
+ startedAt: import_zod30.z.string().optional(),
25434
+ completedAt: import_zod30.z.string().optional(),
25435
+ durationMs: import_zod30.z.number().min(0).optional(),
25436
+ problemCode: import_zod30.z.string().optional(),
25437
+ message: import_zod30.z.string().optional(),
25438
+ kernelSessionId: import_zod30.z.string().nullable().optional(),
25439
+ cleanupSucceeded: import_zod30.z.boolean().nullable().optional()
25143
25440
  }).strict();
25144
- SerpPageSnapshotCaptureSchema = import_zod29.z.object({
25441
+ SerpPageSnapshotCaptureSchema = import_zod30.z.object({
25145
25442
  url: SerpIntelligencePublicHttpUrlSchema,
25146
25443
  requestedUrl: SerpIntelligencePublicHttpUrlSchema,
25147
25444
  finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
25148
- sourceKind: import_zod29.z.enum(SerpPageSnapshotSourceKindValues),
25149
- sourcePosition: import_zod29.z.number().int().min(1).nullable(),
25150
- status: import_zod29.z.enum(SerpPageFetchStatusValues),
25151
- fetchedVia: import_zod29.z.enum(SerpPageFetchedViaValues).nullable(),
25152
- httpStatus: import_zod29.z.number().int().min(100).max(599).nullable(),
25153
- contentType: import_zod29.z.string().nullable(),
25154
- title: import_zod29.z.string().nullable(),
25445
+ sourceKind: import_zod30.z.enum(SerpPageSnapshotSourceKindValues),
25446
+ sourcePosition: import_zod30.z.number().int().min(1).nullable(),
25447
+ status: import_zod30.z.enum(SerpPageFetchStatusValues),
25448
+ fetchedVia: import_zod30.z.enum(SerpPageFetchedViaValues).nullable(),
25449
+ httpStatus: import_zod30.z.number().int().min(100).max(599).nullable(),
25450
+ contentType: import_zod30.z.string().nullable(),
25451
+ title: import_zod30.z.string().nullable(),
25155
25452
  canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
25156
- metaDescription: import_zod29.z.string().nullable(),
25157
- headings: import_zod29.z.array(import_zod29.z.object({
25158
- level: import_zod29.z.number().int().min(1).max(6),
25159
- text: import_zod29.z.string()
25453
+ metaDescription: import_zod30.z.string().nullable(),
25454
+ headings: import_zod30.z.array(import_zod30.z.object({
25455
+ level: import_zod30.z.number().int().min(1).max(6),
25456
+ text: import_zod30.z.string()
25160
25457
  }).strict()).default([]),
25161
- artifact: import_zod29.z.object({
25162
- htmlBlobUrl: import_zod29.z.string().url().nullable(),
25163
- textBlobUrl: import_zod29.z.string().url().nullable(),
25164
- markdownBlobUrl: import_zod29.z.string().url().nullable(),
25165
- screenshotBlobUrl: import_zod29.z.string().url().nullable(),
25166
- contentSha256: import_zod29.z.string().nullable(),
25167
- capturedAt: import_zod29.z.string().nullable()
25458
+ artifact: import_zod30.z.object({
25459
+ htmlBlobUrl: import_zod30.z.string().url().nullable(),
25460
+ textBlobUrl: import_zod30.z.string().url().nullable(),
25461
+ markdownBlobUrl: import_zod30.z.string().url().nullable(),
25462
+ screenshotBlobUrl: import_zod30.z.string().url().nullable(),
25463
+ contentSha256: import_zod30.z.string().nullable(),
25464
+ capturedAt: import_zod30.z.string().nullable()
25168
25465
  }).strict(),
25169
- error: import_zod29.z.object({
25170
- code: import_zod29.z.string(),
25171
- message: import_zod29.z.string()
25466
+ error: import_zod30.z.object({
25467
+ code: import_zod30.z.string(),
25468
+ message: import_zod30.z.string()
25172
25469
  }).strict().nullable()
25173
25470
  }).strict();
25174
- SerpIntelligenceCaptureResponseSchema = import_zod29.z.object({
25471
+ SerpIntelligenceCaptureResponseSchema = import_zod30.z.object({
25175
25472
  harvestResult: SerpIntelligenceHarvestResultSchema,
25176
- attempts: import_zod29.z.array(SerpIntelligenceCaptureAttemptSchema),
25473
+ attempts: import_zod30.z.array(SerpIntelligenceCaptureAttemptSchema),
25177
25474
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
25178
- pageSnapshotArtifacts: import_zod29.z.array(SerpPageSnapshotCaptureSchema),
25179
- billing: import_zod29.z.object({
25180
- creditsUsed: import_zod29.z.number().min(0).optional(),
25181
- requestId: import_zod29.z.string().optional(),
25182
- jobId: import_zod29.z.string().optional()
25475
+ pageSnapshotArtifacts: import_zod30.z.array(SerpPageSnapshotCaptureSchema),
25476
+ billing: import_zod30.z.object({
25477
+ creditsUsed: import_zod30.z.number().min(0).optional(),
25478
+ requestId: import_zod30.z.string().optional(),
25479
+ jobId: import_zod30.z.string().optional()
25183
25480
  }).strict().optional()
25184
25481
  }).strict();
25185
- SerpIntelligencePageSnapshotsResponseSchema = import_zod29.z.object({
25186
- pageSnapshotArtifacts: import_zod29.z.array(SerpPageSnapshotCaptureSchema),
25187
- attempts: import_zod29.z.array(SerpIntelligenceCaptureAttemptSchema).default([])
25482
+ SerpIntelligencePageSnapshotsResponseSchema = import_zod30.z.object({
25483
+ pageSnapshotArtifacts: import_zod30.z.array(SerpPageSnapshotCaptureSchema),
25484
+ attempts: import_zod30.z.array(SerpIntelligenceCaptureAttemptSchema).default([])
25188
25485
  }).strict();
25189
25486
  }
25190
25487
  });
@@ -25408,11 +25705,11 @@ function pageSnapshotTargetsFromBody(body) {
25408
25705
  sourcePosition: null
25409
25706
  }));
25410
25707
  }
25411
- var import_hono11, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, serpIntelligenceApp;
25708
+ var import_hono13, SERP_INTELLIGENCE_RATE_LIMIT, SERP_INTELLIGENCE_RATE_WINDOW_SECONDS, POST_CAPTURE_ROUTE_LABEL, POST_PAGE_SNAPSHOTS_ROUTE_LABEL, serpIntelligenceApp;
25412
25709
  var init_serp_intelligence_routes = __esm({
25413
25710
  "src/api/serp-intelligence-routes.ts"() {
25414
25711
  "use strict";
25415
- import_hono11 = require("hono");
25712
+ import_hono13 = require("hono");
25416
25713
  init_browser_service_env();
25417
25714
  init_page_snapshot_extractor();
25418
25715
  init_serp_capture_service();
@@ -25425,7 +25722,7 @@ var init_serp_intelligence_routes = __esm({
25425
25722
  SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
25426
25723
  POST_CAPTURE_ROUTE_LABEL = "POST /capture";
25427
25724
  POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
25428
- serpIntelligenceApp = new import_hono11.Hono();
25725
+ serpIntelligenceApp = new import_hono13.Hono();
25429
25726
  serpIntelligenceApp.use("*", createApiKeyAuth());
25430
25727
  serpIntelligenceApp.post("/capture", async (c) => {
25431
25728
  void POST_CAPTURE_ROUTE_LABEL;
@@ -25557,7 +25854,7 @@ var PACKAGE_VERSION;
25557
25854
  var init_version = __esm({
25558
25855
  "src/version.ts"() {
25559
25856
  "use strict";
25560
- PACKAGE_VERSION = "0.3.42";
25857
+ PACKAGE_VERSION = "0.3.45";
25561
25858
  }
25562
25859
  });
25563
25860
 
@@ -25633,6 +25930,19 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
25633
25930
  - Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
25634
25931
  - Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
25635
25932
  session on chatgpt.com or claude.ai FIRST, then run it against that session).
25933
+ - Not sure which workflow fits a goal -> **workflow_suggest**; to just see what's available -> **workflow_list**.
25934
+ - Hosted/stepwise chain: **workflow_run** starts and returns \`runId\` (+ \`nextStep\` when multi-leg) ->
25935
+ **workflow_step** with that \`runId\` advances one leg at a time until \`done\` -> **workflow_status**
25936
+ re-opens a run to check state or recover artifact ids -> **workflow_artifact_read** pulls one artifact's
25937
+ full content by \`runId\` + \`artifactId\`.
25938
+ - Browser replay recording: **browser_replay_start** begins it -> **browser_replay_mark** (while
25939
+ recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
25940
+ ends it -> feed collected annotations to **browser_replay_annotate**, or just
25941
+ **browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
25942
+ - Video breakdown is async: **video_frame_analysis** takes a DIRECT media file URL (resolve
25943
+ YouTube/Facebook/Instagram page URLs to one first, e.g. via \`facebook_page_intel\`'s \`videoUrl\`) and
25944
+ returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
25945
+ is \`done\`.
25636
25946
 
25637
25947
  ## Notes
25638
25948
  - Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
@@ -25646,183 +25956,193 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
25646
25956
  });
25647
25957
 
25648
25958
  // src/mcp/mcp-tool-schemas.ts
25649
- var import_zod30, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
25959
+ var import_zod31, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
25650
25960
  var init_mcp_tool_schemas = __esm({
25651
25961
  "src/mcp/mcp-tool-schemas.ts"() {
25652
25962
  "use strict";
25653
- import_zod30 = require("zod");
25963
+ import_zod31 = require("zod");
25654
25964
  init_schemas3();
25655
25965
  HarvestPaaInputSchema = {
25656
- query: import_zod30.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
25657
- location: import_zod30.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
25658
- maxQuestions: import_zod30.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
25659
- gl: import_zod30.z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
25660
- hl: import_zod30.z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
25661
- device: import_zod30.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
25662
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic (clean egress by default; residential only when a location is given, with fallback). Set "location" only to force local-pack targeting.'),
25663
- proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
25664
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
25966
+ query: import_zod31.z.string().min(1).describe('The search query. KEEP the place in the query text for localized results (e.g. "best hvac company Denver CO") and also set location \u2014 city-in-query is what localizes reliably.'),
25967
+ location: import_zod31.z.string().optional().describe('City, region, or country for geo signals, e.g. "Denver, CO". Set alongside city-in-query wording; alone it does NOT reliably localize.'),
25968
+ maxQuestions: import_zod31.z.number().int().min(1).max(200).default(30).describe("PAA questions to extract. Default 30, maximum 200. Use 10 for quick probes, 100-200 for deep research. Billed per extracted question; unused hold refunded."),
25969
+ gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
25970
+ hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from the user request."),
25971
+ device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
25972
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP for rank-tracking fidelity, is frequently CAPTCHA-blocked, and accepts failures.'),
25973
+ proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
25974
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior.")
25665
25975
  };
25666
25976
  ExtractUrlInputSchema = {
25667
- url: import_zod30.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
25668
- screenshot: import_zod30.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
25669
- screenshotDevice: import_zod30.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
25670
- extractBranding: import_zod30.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
25671
- downloadMedia: import_zod30.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
25672
- mediaTypes: import_zod30.z.array(import_zod30.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
25673
- allowLocal: import_zod30.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only."),
25674
- depositToVault: import_zod30.z.boolean().default(false).describe("Also save the full page content into the user's MCP Memory Library vault, server-side. The complete body is stored and embedded for semantic recall WITHOUT passing through this conversation \u2014 the tool result only confirms the saved note. Use when the user asks to save/remember/store a page, or to build a knowledge base from scrapes."),
25675
- vaultName: import_zod30.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
25977
+ url: import_zod31.z.string().url().describe("Public http/https URL to extract."),
25978
+ screenshot: import_zod31.z.boolean().default(false).describe("Capture a full-page screenshot, saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline."),
25979
+ screenshotDevice: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900, mobile = 390\xD7844."),
25980
+ extractBranding: import_zod31.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon via a rendered browser session."),
25981
+ downloadMedia: import_zod31.z.boolean().default(false).describe("Extract and download page media (images/video/audio) to ~/Downloads/mcp-scraper/media/. Ad/tracking noise is filtered automatically."),
25982
+ mediaTypes: import_zod31.z.array(import_zod31.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
25983
+ allowLocal: import_zod31.z.boolean().default(false).describe("Allow localhost and private-network URLs. Local development only."),
25984
+ depositToVault: import_zod31.z.boolean().default(false).describe("Save the full page content into the user's MCP Memory vault server-side, embedded for semantic recall \u2014 the full body is NOT returned to chat."),
25985
+ vaultName: import_zod31.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
25676
25986
  };
25677
25987
  MapSiteUrlsInputSchema = {
25678
- url: import_zod30.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
25679
- maxUrls: import_zod30.z.number().int().min(1).max(1e4).optional().describe("Maximum URLs to discover. Use 100 for normal maps, up to 10000 for a full inventory. Large maps (over 500 URLs) write the complete inventory to a local file and return only a summary plus the file path instead of the full list inline.")
25988
+ url: import_zod31.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
25989
+ maxUrls: import_zod31.z.number().int().min(1).max(1e4).optional().describe("Maximum URLs to discover. Use 100 for normal maps, up to 10000 for a full inventory. Large maps (over 500 URLs) write the complete inventory to a local file and return only a summary plus the file path instead of the full list inline.")
25680
25990
  };
25681
25991
  ExtractSiteInputSchema = {
25682
- url: import_zod30.z.string().url().describe("Public website URL or domain to crawl for page CONTENT across multiple pages (map + scrape). Use when the user wants the content/text/markdown of a site's pages. For a technical SEO audit (issues, link graph, indexability, headings, image weights) use audit_site instead \u2014 extract_site returns content only, not analysis."),
25683
- maxPages: import_zod30.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract. Use 50 for a normal crawl, up to 10000 for a full-site bulk scrape. Bulk crawls (over 25 pages) switch to folder mode: every page is saved as its own Markdown file in a local folder and the response returns only a summary plus the folder path, so the full content never floods the context window."),
25684
- rotateProxies: import_zod30.z.boolean().optional().describe("Route page fetches through rotating residential proxies in headful browsers to defeat rate-limiting and bot blocks (403/429). Discovers URLs from the sitemap, fetches in batches with a fresh proxy per batch, retries failures on a new proxy, and automatically parallelizes across the account's concurrency slots. Slower and pricier than the default fetch path, so use only when a site blocks normal crawling."),
25685
- rotateProxyEvery: import_zod30.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, how many pages to fetch per browser/proxy before discarding it and minting a fresh one. Default 30. Lower values rotate IPs more aggressively against strict rate limits."),
25686
- formats: import_zod30.z.array(import_zod30.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Which data formats to include in the per-page output. markdown=page content as Markdown, links=internal/external link graph, json=structured data/schema, images=per-page image URLs (links only, no downloads), branding=site-level logo/colors/fonts captured once from the homepage (requires a browser, adds time). markdown, links, json, and images are always captured cheaply from the HTML; branding is the only opt-in capture. Defaults to markdown+links when omitted.")
25992
+ url: import_zod31.z.string().url().describe("Public website URL or domain to crawl for page CONTENT (map + scrape). For a technical SEO audit use audit_site instead \u2014 this returns content only, not analysis."),
25993
+ maxPages: import_zod31.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract. Bulk crawls (over 25 pages) switch to folder mode: each page saved as its own Markdown file, with a summary plus folder path returned instead of inlining content."),
25994
+ rotateProxies: import_zod31.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks (403/429). Slower and pricier \u2014 use only when a site blocks normal crawling."),
25995
+ rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
25996
+ formats: import_zod31.z.array(import_zod31.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Per-page output formats: markdown, links, json, images are captured cheaply from HTML; branding (site-level logo/colors/fonts) requires a browser and adds time. Defaults to markdown+links.")
25687
25997
  };
25688
25998
  AuditSiteInputSchema = {
25689
- url: import_zod30.z.string().url().describe("Public website URL or domain to run a full technical SEO audit on. Use when the user asks for a technical audit, SEO audit, site health check, or a Screaming-Frog-style crawl \u2014 i.e. they want ANALYSIS (issues, internal link graph, indexability, heading breakdown, image sizes/formats), not just page content. For plain content scraping use extract_site instead."),
25690
- maxPages: import_zod30.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit. Use 50 for a normal audit, up to 10000 for a full-site audit. The audit always writes a folder of analysis files (report.md, issues.json, pages.jsonl, links.jsonl, link-metrics.jsonl, images.jsonl) plus per-page content, and returns a headline summary plus the folder path."),
25691
- rotateProxies: import_zod30.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks (403/429). Slower and pricier than the default fetch path, so use only when a site blocks normal crawling."),
25692
- rotateProxyEvery: import_zod30.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, how many pages to fetch per browser/proxy before discarding it and minting a fresh one. Default 30.")
25999
+ url: import_zod31.z.string().url().describe("Public website URL or domain for a full technical SEO audit (issues, link graph, indexability, headings, images). For plain content use extract_site instead."),
26000
+ maxPages: import_zod31.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit. Always writes a folder of analysis files plus per-page content, returning a summary plus the folder path."),
26001
+ rotateProxies: import_zod31.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks. Slower/pricier \u2014 use only when a site blocks normal crawling."),
26002
+ rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30.")
25693
26003
  };
25694
26004
  YoutubeHarvestInputSchema = {
25695
- mode: import_zod30.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
25696
- query: import_zod30.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
25697
- channelHandle: import_zod30.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
25698
- maxVideos: import_zod30.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500. Use 10-25 for quick topic discovery, 50 for normal channel/search harvests, and larger values only when the user asks for full channel/history because large responses consume more context.")
26005
+ mode: import_zod31.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
26006
+ query: import_zod31.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
26007
+ channelHandle: import_zod31.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
26008
+ maxVideos: import_zod31.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.")
25699
26009
  };
25700
26010
  YoutubeTranscribeInputSchema = {
25701
- videoId: import_zod30.z.string().min(1).optional().describe("YouTube video ID, e.g. dQw4w9WgXcQ. Use only an ID returned by youtube_harvest or visible in a YouTube URL; do not invent one."),
25702
- url: import_zod30.z.string().url().optional().describe("Full YouTube URL, e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ or https://youtu.be/dQw4w9WgXcQ. Use this when the user pasted a URL instead of an ID. Provide videoId or url.")
26011
+ videoId: import_zod31.z.string().min(1).optional().describe("YouTube video ID, e.g. dQw4w9WgXcQ. Use only an ID returned by youtube_harvest or visible in a YouTube URL; do not invent one."),
26012
+ url: import_zod31.z.string().url().optional().describe("Full YouTube URL. Use when the user pasted a URL instead of an ID. Provide videoId or url.")
25703
26013
  };
25704
26014
  FacebookPageIntelInputSchema = {
25705
- pageId: import_zod30.z.string().optional().describe("Facebook advertiser/page ID. Use only a pageId returned by facebook_ad_search/facebook_page_intel or copied from Facebook Ad Library; do not construct one yourself."),
25706
- libraryId: import_zod30.z.string().optional().describe("Facebook Ad Library archive ID for a known ad or advertiser sample. Use a libraryId returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
25707
- query: import_zod30.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
25708
- maxAds: import_zod30.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200. Prefer 25-50 for focused advertiser scans; use 100-200 only when the user asks for a broad ad archive sweep."),
25709
- country: import_zod30.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU. Infer from the user request when they name a country.")
26015
+ pageId: import_zod31.z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
26016
+ libraryId: import_zod31.z.string().optional().describe("Facebook Ad Library archive ID. Use a value returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
26017
+ query: import_zod31.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
26018
+ maxAds: import_zod31.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
26019
+ country: import_zod31.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
25710
26020
  };
25711
26021
  FacebookAdSearchInputSchema = {
25712
- query: import_zod30.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
25713
- country: import_zod30.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
25714
- maxResults: import_zod30.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
26022
+ query: import_zod31.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
26023
+ country: import_zod31.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
26024
+ maxResults: import_zod31.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
25715
26025
  };
25716
26026
  RedditThreadInputSchema = {
25717
- url: import_zod30.z.string().min(1).describe("A reddit.com thread/post URL (www, old, or new Reddit, or a redd.it link). The service fetches it via old.reddit through a residential proxy and returns the post plus its comment tree."),
25718
- maxComments: import_zod30.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
26027
+ url: import_zod31.z.string().min(1).describe("A reddit.com thread/post URL (www, old, new Reddit, or redd.it)."),
26028
+ maxComments: import_zod31.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
26029
+ };
26030
+ VideoFrameAnalysisInputSchema = {
26031
+ sourceUrl: import_zod31.z.string().min(1).describe("A DIRECT video file URL (.mp4/.webm/.mov). Not a YouTube/Facebook/Instagram page URL \u2014 resolve those to a direct media URL first. Videos up to 30 minutes are supported."),
26032
+ intervalS: import_zod31.z.number().min(1).max(30).optional().describe("Preferred seconds between sampled frames (1-30, default 2). Automatically widened for long videos so the whole duration is covered within the frame budget."),
26033
+ maxFrames: import_zod31.z.number().int().min(1).max(120).optional().describe("Max frames analyzed (<=120, default 120). Frames are spread evenly across the whole video."),
26034
+ detail: import_zod31.z.enum(["fast", "standard", "deep"]).optional().describe("Analysis depth. Default standard."),
26035
+ vault: import_zod31.z.string().min(1).optional().describe('Memory vault to save the finished breakdown into. Default "Library".')
26036
+ };
26037
+ VideoFrameAnalysisStatusInputSchema = {
26038
+ runId: import_zod31.z.string().min(1).describe("The runId returned by video_frame_analysis.")
25719
26039
  };
25720
26040
  FacebookAdTranscribeInputSchema = {
25721
- videoUrl: import_zod30.z.string().url().describe("Direct Facebook CDN video URL from a facebook_page_intel ad result. Do not pass a public Facebook reel/post/share URL here; use facebook_video_transcribe for organic Facebook URLs.")
26041
+ videoUrl: import_zod31.z.string().url().describe("Direct Facebook CDN video URL from facebook_page_intel. Do not pass a public post/reel/share URL \u2014 use facebook_video_transcribe for those.")
25722
26042
  };
25723
26043
  FacebookVideoTranscribeInputSchema = {
25724
- url: import_zod30.z.string().url().describe("Organic Facebook reel, video, watch, post, or share URL from facebook.com, m.facebook.com, or fb.watch. The tool renders the page, extracts the best matching public Facebook CDN MP4 URL, then transcribes it. Use this when the user pastes a normal Facebook video page URL and asks for the transcript or downloadable MP4."),
25725
- quality: import_zod30.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
26044
+ url: import_zod31.z.string().url().describe("Organic Facebook reel/video/watch/post/share URL from facebook.com, m.facebook.com, or fb.watch."),
26045
+ quality: import_zod31.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
25726
26046
  };
25727
26047
  GoogleAdsSearchInputSchema = {
25728
- query: import_zod30.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in the Google Ads Transparency Center. Domains resolve to the advertisers running ads that point to that site."),
25729
- region: import_zod30.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
25730
- maxResults: import_zod30.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
26048
+ query: import_zod31.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in Google Ads Transparency Center."),
26049
+ region: import_zod31.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
26050
+ maxResults: import_zod31.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
25731
26051
  };
25732
26052
  GoogleAdsPageIntelInputSchema = {
25733
- advertiserId: import_zod30.z.string().optional().describe("Google Ads Transparency advertiser ID (starts with AR...). Use one returned by google_ads_search; do not construct one yourself."),
25734
- domain: import_zod30.z.string().optional().describe("A domain (e.g. getviktor.com) whose primary advertiser to inspect when advertiserId is unknown. One of advertiserId or domain is required."),
25735
- region: import_zod30.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US."),
25736
- maxAds: import_zod30.z.number().int().min(1).max(200).default(50).describe("Maximum creatives to inspect and hydrate. Default 50, maximum 200. Prefer 25-50 for focused scans.")
26053
+ advertiserId: import_zod31.z.string().optional().describe("Google Ads Transparency advertiser ID (starts with AR...). Use one returned by google_ads_search; do not construct one yourself."),
26054
+ domain: import_zod31.z.string().optional().describe("A domain (e.g. getviktor.com) whose primary advertiser to inspect when advertiserId is unknown. One of advertiserId or domain is required."),
26055
+ region: import_zod31.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US."),
26056
+ maxAds: import_zod31.z.number().int().min(1).max(200).default(50).describe("Maximum creatives to inspect and hydrate. Default 50, maximum 200. Prefer 25-50 for focused scans.")
25737
26057
  };
25738
26058
  GoogleAdsTranscribeInputSchema = {
25739
- videoUrl: import_zod30.z.string().url().describe("Direct googlevideo.com playback URL from a google_ads_page_intel video ad result (the videoUrl field). For YouTube-hosted ads use youtube_transcribe with the returned youtubeVideoId instead.")
26059
+ videoUrl: import_zod31.z.string().url().describe("Direct googlevideo.com playback URL from google_ads_page_intel. For YouTube-hosted ads use youtube_transcribe instead.")
25740
26060
  };
25741
26061
  InstagramProfileContentInputSchema = {
25742
- handle: import_zod30.z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
25743
- url: import_zod30.z.string().url().optional().describe("Instagram profile URL, e.g. https://www.instagram.com/nasaartemis/. Provide handle or url."),
25744
- profile: import_zod30.z.string().min(1).optional().describe("Optional saved hosted browser profile name to load authenticated Instagram access. If omitted, the server uses its configured default profile when present."),
25745
- saveProfileChanges: import_zod30.z.boolean().optional().describe("Whether to save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login session."),
25746
- maxItems: import_zod30.z.number().int().min(1).max(2e3).default(50).describe("Maximum profile grid post/reel/tv URLs to collect. Default 50, maximum 2000. Use higher values only when the user asks for a fuller archive."),
25747
- maxScrolls: import_zod30.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250. Increase for long profiles when Instagram continues loading more grid links."),
25748
- scrollDelayMs: import_zod30.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each pagination scroll before collecting newly loaded links. Default 1200ms. Increase to 2000-3000ms when Instagram loads slowly."),
25749
- stableScrollLimit: import_zod30.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links or scroll progress. Default 4.")
26062
+ handle: import_zod31.z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
26063
+ url: import_zod31.z.string().url().optional().describe("Instagram profile URL. Provide handle or url."),
26064
+ profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
26065
+ saveProfileChanges: import_zod31.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
26066
+ maxItems: import_zod31.z.number().int().min(1).max(2e3).default(50).describe("Maximum grid URLs to collect. Default 50, maximum 2000."),
26067
+ maxScrolls: import_zod31.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250."),
26068
+ scrollDelayMs: import_zod31.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each scroll before collecting new links. Default 1200ms."),
26069
+ stableScrollLimit: import_zod31.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links.")
25750
26070
  };
25751
26071
  InstagramMediaDownloadInputSchema = {
25752
- url: import_zod30.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/. The tool renders the page, extracts text, image metadata, and Instagram CDN media tracks."),
25753
- profile: import_zod30.z.string().min(1).optional().describe("Optional saved hosted browser profile name to load authenticated Instagram access. If omitted, the server uses its configured default profile when present."),
25754
- saveProfileChanges: import_zod30.z.boolean().optional().describe("Whether to save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login session."),
25755
- mediaTypes: import_zod30.z.array(import_zod30.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true. Reels commonly expose separate video-only and audio-only MP4 tracks."),
25756
- downloadMedia: import_zod30.z.boolean().default(true).describe("Download extracted text/media files to the MCP Scraper output directory when the API server can write files. Always returns extracted media URLs even when false."),
25757
- downloadAllTracks: import_zod30.z.boolean().default(false).describe("Download every captured Instagram MP4 track instead of only the selected best video and audio tracks. Use false by default to avoid duplicate bitrates."),
25758
- includeTranscript: import_zod30.z.boolean().default(false).describe("Transcribe the selected audio track when available. This adds transcription cost and may take longer."),
25759
- mux: import_zod30.z.boolean().default(true).describe("When video and audio tracks are downloaded separately, try to mux them into a single MP4 if ffmpeg is available. Returns separate tracks when muxing is unavailable.")
26072
+ url: import_zod31.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/."),
26073
+ profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
26074
+ saveProfileChanges: import_zod31.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
26075
+ mediaTypes: import_zod31.z.array(import_zod31.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true."),
26076
+ downloadMedia: import_zod31.z.boolean().default(true).describe("Download extracted text/media files to the output directory. Media URLs are always returned even when false."),
26077
+ downloadAllTracks: import_zod31.z.boolean().default(false).describe("Download every captured MP4 track instead of only the best video/audio pair."),
26078
+ includeTranscript: import_zod31.z.boolean().default(false).describe("Transcribe the selected audio track. Adds transcription cost and time."),
26079
+ mux: import_zod31.z.boolean().default(true).describe("Mux separately downloaded video/audio tracks into one MP4 if ffmpeg is available.")
25760
26080
  };
25761
26081
  MapsPlaceIntelInputSchema = {
25762
- businessName: import_zod30.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
25763
- location: import_zod30.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
25764
- gl: import_zod30.z.string().length(2).default("us").describe("Google country code inferred from location."),
25765
- hl: import_zod30.z.string().length(2).default("en").describe("Language inferred from user request."),
25766
- includeReviews: import_zod30.z.boolean().default(false).describe("Whether to fetch individual review cards. Use true when the user asks for reviews, customer pain, complaints, praise, themes, or review evidence."),
25767
- maxReviews: import_zod30.z.number().int().min(1).max(500).default(50).describe("Max review cards to return when includeReviews is true. Default 50, maximum 500. Use 50 for normal review analysis and larger values only for deep review mining.")
26082
+ businessName: import_zod31.z.string().min(1).describe('Business name only, e.g. "Elite Roofing" (not "Elite Roofing Denver CO" \u2014 put the city in location).'),
26083
+ location: import_zod31.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".'),
26084
+ gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
26085
+ hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
26086
+ includeReviews: import_zod31.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
26087
+ maxReviews: import_zod31.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
25768
26088
  };
25769
26089
  MapsSearchInputSchema = {
25770
- query: import_zod30.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
25771
- location: import_zod30.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
25772
- gl: import_zod30.z.string().length(2).default("us").describe("Google country code inferred from location."),
25773
- hl: import_zod30.z.string().length(2).default("en").describe("Language inferred from user request."),
25774
- maxResults: import_zod30.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
25775
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode. Maps defaults to location so local market searches get city/state residential proxy targeting and rotation. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
25776
- proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
25777
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
26090
+ 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.'),
26091
+ location: import_zod31.z.string().optional().describe('City, region, country, or service area, e.g. "Denver, CO".'),
26092
+ gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
26093
+ hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
26094
+ maxResults: import_zod31.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
26095
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Defaults to location (city/state residential proxy targeting). configured forces the service proxy without city/ZIP targeting; none is local debugging only."),
26096
+ proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential proxy targeting."),
26097
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
25778
26098
  };
25779
26099
  DirectoryWorkflowInputSchema = {
25780
- query: import_zod30.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every selected market, e.g. roofers, dentists, med spas. Do not include the city here."),
25781
- state: import_zod30.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
25782
- minPopulation: import_zod30.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
25783
- populationYear: import_zod30.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
25784
- maxCities: import_zod30.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
25785
- maxResultsPerCity: import_zod30.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
25786
- concurrency: import_zod30.z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
25787
- includeZipGroups: import_zod30.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
25788
- usZipsCsvPath: import_zod30.z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
25789
- saveCsv: import_zod30.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
25790
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode for every city Maps search. Maps workflows default to location so each city can use city/state or ZIP-group residential proxy targeting. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
25791
- proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its ZIP group or city/state location."),
25792
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
26100
+ query: import_zod31.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every market. Do not include the city."),
26101
+ state: import_zod31.z.string().min(2).default("TN").describe("US state abbreviation or name used to select Census places, e.g. TN."),
26102
+ minPopulation: import_zod31.z.number().int().min(0).default(1e5).describe("Minimum Census place population for market selection."),
26103
+ populationYear: import_zod31.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year (2020-2025 Population Estimates Program)."),
26104
+ maxCities: import_zod31.z.number().int().min(1).max(100).default(25).describe("Maximum markets to process after sorting by population descending."),
26105
+ maxResultsPerCity: import_zod31.z.number().int().min(1).max(50).default(50).describe("Google Maps candidates to collect per city."),
26106
+ concurrency: import_zod31.z.number().int().min(1).max(5).default(5).describe("City Maps searches to run in parallel."),
26107
+ includeZipGroups: import_zod31.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
26108
+ usZipsCsvPath: import_zod31.z.string().optional().describe("Local/test-only path to a US ZIPS CSV (state_abbr, zipcode, county, city columns). Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
26109
+ saveCsv: import_zod31.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
26110
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting per city search. Defaults to location (city/ZIP-group residential targeting); configured forces the service proxy; none is local debugging only."),
26111
+ proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
26112
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
25793
26113
  };
25794
- RankTrackerModeSchema = import_zod30.z.enum(["maps", "organic", "ai_overview", "paa"]);
26114
+ RankTrackerModeSchema = import_zod31.z.enum(["maps", "organic", "ai_overview", "paa"]);
25795
26115
  RankTrackerBlueprintInputSchema = {
25796
- projectName: import_zod30.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
25797
- targetDomain: import_zod30.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
25798
- targetBusinessName: import_zod30.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
25799
- trackingModes: import_zod30.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
25800
- keywords: import_zod30.z.array(import_zod30.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
25801
- locations: import_zod30.z.array(import_zod30.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
25802
- competitors: import_zod30.z.array(import_zod30.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
25803
- database: import_zod30.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
25804
- scheduleCadence: import_zod30.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
25805
- customCron: import_zod30.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
25806
- timezone: import_zod30.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
25807
- includeCron: import_zod30.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
25808
- includeDashboard: import_zod30.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
25809
- includeAlerts: import_zod30.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
25810
- notes: import_zod30.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
26116
+ projectName: import_zod31.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
26117
+ targetDomain: import_zod31.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources."),
26118
+ targetBusinessName: import_zod31.z.string().min(1).optional().describe("Primary Google Business Profile/brand name to match in Maps results. Required for Maps tracking."),
26119
+ trackingModes: import_zod31.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build: maps, organic, ai_overview, paa."),
26120
+ keywords: import_zod31.z.array(import_zod31.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty to derive from user input downstream."),
26121
+ locations: import_zod31.z.array(import_zod31.z.string().min(1)).max(100).default([]).describe('Markets, cities, ZIPs, or service areas to track, e.g. "Denver, CO".'),
26122
+ competitors: import_zod31.z.array(import_zod31.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
26123
+ database: import_zod31.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family to target when generating migrations."),
26124
+ scheduleCadence: import_zod31.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence."),
26125
+ customCron: import_zod31.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
26126
+ timezone: import_zod31.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks."),
26127
+ includeCron: import_zod31.z.boolean().default(true).describe("Include a cron/heartbeat worker plan."),
26128
+ includeDashboard: import_zod31.z.boolean().default(true).describe("Include dashboard/reporting requirements."),
26129
+ includeAlerts: import_zod31.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature changes."),
26130
+ notes: import_zod31.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements.")
25811
26131
  };
25812
- NullableString = import_zod30.z.string().nullable();
25813
- MapsSearchAttemptOutput = import_zod30.z.object({
25814
- attemptNumber: import_zod30.z.number().int().min(1),
25815
- maxAttempts: import_zod30.z.number().int().min(1),
25816
- status: import_zod30.z.enum(["ok", "failed"]),
25817
- outcome: import_zod30.z.string(),
25818
- willRetry: import_zod30.z.boolean(),
25819
- durationMs: import_zod30.z.number().int().min(0),
25820
- resultCount: import_zod30.z.number().int().min(0),
26132
+ NullableString = import_zod31.z.string().nullable();
26133
+ MapsSearchAttemptOutput = import_zod31.z.object({
26134
+ attemptNumber: import_zod31.z.number().int().min(1),
26135
+ maxAttempts: import_zod31.z.number().int().min(1),
26136
+ status: import_zod31.z.enum(["ok", "failed"]),
26137
+ outcome: import_zod31.z.string(),
26138
+ willRetry: import_zod31.z.boolean(),
26139
+ durationMs: import_zod31.z.number().int().min(0),
26140
+ resultCount: import_zod31.z.number().int().min(0),
25821
26141
  error: NullableString,
25822
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]),
25823
- proxyResolutionSource: import_zod30.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
26142
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]),
26143
+ proxyResolutionSource: import_zod31.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
25824
26144
  proxyIdSuffix: NullableString,
25825
- proxyTargetLevel: import_zod30.z.enum(["zip", "city", "state"]).nullable(),
26145
+ proxyTargetLevel: import_zod31.z.enum(["zip", "city", "state"]).nullable(),
25826
26146
  proxyTargetLocation: NullableString,
25827
26147
  proxyTargetZip: NullableString,
25828
26148
  browserSessionIdSuffix: NullableString,
@@ -25831,17 +26151,17 @@ var init_mcp_tool_schemas = __esm({
25831
26151
  observedRegion: NullableString
25832
26152
  });
25833
26153
  MapsSearchOutputSchema = {
25834
- query: import_zod30.z.string(),
25835
- location: import_zod30.z.string().nullable(),
25836
- searchQuery: import_zod30.z.string(),
25837
- searchUrl: import_zod30.z.string().url(),
25838
- extractedAt: import_zod30.z.string(),
25839
- requestedMaxResults: import_zod30.z.number().int().min(1).max(50),
25840
- resultCount: import_zod30.z.number().int().min(0).max(50),
25841
- results: import_zod30.z.array(import_zod30.z.object({
25842
- position: import_zod30.z.number().int().min(1),
25843
- name: import_zod30.z.string(),
25844
- placeUrl: import_zod30.z.string().url(),
26154
+ query: import_zod31.z.string(),
26155
+ location: import_zod31.z.string().nullable(),
26156
+ searchQuery: import_zod31.z.string(),
26157
+ searchUrl: import_zod31.z.string().url(),
26158
+ extractedAt: import_zod31.z.string(),
26159
+ requestedMaxResults: import_zod31.z.number().int().min(1).max(50),
26160
+ resultCount: import_zod31.z.number().int().min(0).max(50),
26161
+ results: import_zod31.z.array(import_zod31.z.object({
26162
+ position: import_zod31.z.number().int().min(1),
26163
+ name: import_zod31.z.string(),
26164
+ placeUrl: import_zod31.z.string().url(),
25845
26165
  cid: NullableString,
25846
26166
  cidDecimal: NullableString,
25847
26167
  rating: NullableString,
@@ -25852,15 +26172,15 @@ var init_mcp_tool_schemas = __esm({
25852
26172
  hoursStatus: NullableString,
25853
26173
  websiteUrl: NullableString,
25854
26174
  directionsUrl: NullableString,
25855
- metadata: import_zod30.z.array(import_zod30.z.string())
26175
+ metadata: import_zod31.z.array(import_zod31.z.string())
25856
26176
  })),
25857
- attempts: import_zod30.z.array(MapsSearchAttemptOutput),
25858
- durationMs: import_zod30.z.number().int().min(0)
26177
+ attempts: import_zod31.z.array(MapsSearchAttemptOutput),
26178
+ durationMs: import_zod31.z.number().int().min(0)
25859
26179
  };
25860
- DirectoryMapsBusinessOutput = import_zod30.z.object({
25861
- position: import_zod30.z.number().int().min(1),
25862
- name: import_zod30.z.string(),
25863
- placeUrl: import_zod30.z.string().url(),
26180
+ DirectoryMapsBusinessOutput = import_zod31.z.object({
26181
+ position: import_zod31.z.number().int().min(1),
26182
+ name: import_zod31.z.string(),
26183
+ placeUrl: import_zod31.z.string().url(),
25864
26184
  cid: NullableString,
25865
26185
  cidDecimal: NullableString,
25866
26186
  rating: NullableString,
@@ -25871,120 +26191,120 @@ var init_mcp_tool_schemas = __esm({
25871
26191
  hoursStatus: NullableString,
25872
26192
  websiteUrl: NullableString,
25873
26193
  directionsUrl: NullableString,
25874
- metadata: import_zod30.z.array(import_zod30.z.string())
26194
+ metadata: import_zod31.z.array(import_zod31.z.string())
25875
26195
  });
25876
26196
  DirectoryWorkflowOutputSchema = {
25877
- query: import_zod30.z.string(),
25878
- state: import_zod30.z.string(),
25879
- minPopulation: import_zod30.z.number().int().min(0),
25880
- populationYear: import_zod30.z.number().int().min(2020).max(2025),
25881
- maxResultsPerCity: import_zod30.z.number().int().min(1).max(50),
25882
- concurrency: import_zod30.z.number().int().min(1).max(5),
25883
- censusSourceUrl: import_zod30.z.string().url(),
26197
+ query: import_zod31.z.string(),
26198
+ state: import_zod31.z.string(),
26199
+ minPopulation: import_zod31.z.number().int().min(0),
26200
+ populationYear: import_zod31.z.number().int().min(2020).max(2025),
26201
+ maxResultsPerCity: import_zod31.z.number().int().min(1).max(50),
26202
+ concurrency: import_zod31.z.number().int().min(1).max(5),
26203
+ censusSourceUrl: import_zod31.z.string().url(),
25884
26204
  usZipsSourcePath: NullableString,
25885
- warnings: import_zod30.z.array(import_zod30.z.string()),
25886
- extractedAt: import_zod30.z.string(),
25887
- selectedCityCount: import_zod30.z.number().int().min(0),
25888
- totalResultCount: import_zod30.z.number().int().min(0),
26205
+ warnings: import_zod31.z.array(import_zod31.z.string()),
26206
+ extractedAt: import_zod31.z.string(),
26207
+ selectedCityCount: import_zod31.z.number().int().min(0),
26208
+ totalResultCount: import_zod31.z.number().int().min(0),
25889
26209
  csvPath: NullableString,
25890
- cities: import_zod30.z.array(import_zod30.z.object({
25891
- city: import_zod30.z.string(),
25892
- state: import_zod30.z.string(),
25893
- location: import_zod30.z.string(),
25894
- cityKey: import_zod30.z.string(),
25895
- censusName: import_zod30.z.string(),
25896
- population: import_zod30.z.number().int().min(0),
25897
- populationYear: import_zod30.z.number().int().min(2020).max(2025),
25898
- zips: import_zod30.z.array(import_zod30.z.string()),
25899
- counties: import_zod30.z.array(import_zod30.z.string()),
25900
- status: import_zod30.z.enum(["ok", "empty", "failed"]),
26210
+ cities: import_zod31.z.array(import_zod31.z.object({
26211
+ city: import_zod31.z.string(),
26212
+ state: import_zod31.z.string(),
26213
+ location: import_zod31.z.string(),
26214
+ cityKey: import_zod31.z.string(),
26215
+ censusName: import_zod31.z.string(),
26216
+ population: import_zod31.z.number().int().min(0),
26217
+ populationYear: import_zod31.z.number().int().min(2020).max(2025),
26218
+ zips: import_zod31.z.array(import_zod31.z.string()),
26219
+ counties: import_zod31.z.array(import_zod31.z.string()),
26220
+ status: import_zod31.z.enum(["ok", "empty", "failed"]),
25901
26221
  error: NullableString,
25902
- resultCount: import_zod30.z.number().int().min(0),
25903
- durationMs: import_zod30.z.number().int().min(0),
25904
- attempts: import_zod30.z.array(MapsSearchAttemptOutput),
25905
- results: import_zod30.z.array(DirectoryMapsBusinessOutput)
26222
+ resultCount: import_zod31.z.number().int().min(0),
26223
+ durationMs: import_zod31.z.number().int().min(0),
26224
+ attempts: import_zod31.z.array(MapsSearchAttemptOutput),
26225
+ results: import_zod31.z.array(DirectoryMapsBusinessOutput)
25906
26226
  })),
25907
- durationMs: import_zod30.z.number().int().min(0)
26227
+ durationMs: import_zod31.z.number().int().min(0)
25908
26228
  };
25909
- RankTrackerToolPlanOutput = import_zod30.z.object({
25910
- tool: import_zod30.z.string(),
25911
- purpose: import_zod30.z.string()
26229
+ RankTrackerToolPlanOutput = import_zod31.z.object({
26230
+ tool: import_zod31.z.string(),
26231
+ purpose: import_zod31.z.string()
25912
26232
  });
25913
- RankTrackerTableOutput = import_zod30.z.object({
25914
- name: import_zod30.z.string(),
25915
- purpose: import_zod30.z.string(),
25916
- keyColumns: import_zod30.z.array(import_zod30.z.string())
26233
+ RankTrackerTableOutput = import_zod31.z.object({
26234
+ name: import_zod31.z.string(),
26235
+ purpose: import_zod31.z.string(),
26236
+ keyColumns: import_zod31.z.array(import_zod31.z.string())
25917
26237
  });
25918
- RankTrackerCronJobOutput = import_zod30.z.object({
25919
- name: import_zod30.z.string(),
25920
- purpose: import_zod30.z.string(),
25921
- modes: import_zod30.z.array(RankTrackerModeSchema),
25922
- recommendedTools: import_zod30.z.array(import_zod30.z.string())
26238
+ RankTrackerCronJobOutput = import_zod31.z.object({
26239
+ name: import_zod31.z.string(),
26240
+ purpose: import_zod31.z.string(),
26241
+ modes: import_zod31.z.array(RankTrackerModeSchema),
26242
+ recommendedTools: import_zod31.z.array(import_zod31.z.string())
25923
26243
  });
25924
26244
  RankTrackerBlueprintOutputSchema = {
25925
- projectName: import_zod30.z.string(),
26245
+ projectName: import_zod31.z.string(),
25926
26246
  targetDomain: NullableString,
25927
26247
  targetBusinessName: NullableString,
25928
- trackingModes: import_zod30.z.array(RankTrackerModeSchema),
25929
- database: import_zod30.z.string(),
25930
- recommendedTools: import_zod30.z.array(RankTrackerToolPlanOutput),
25931
- tables: import_zod30.z.array(RankTrackerTableOutput),
25932
- cron: import_zod30.z.object({
25933
- enabled: import_zod30.z.boolean(),
25934
- cadence: import_zod30.z.string(),
25935
- expression: import_zod30.z.string(),
25936
- timezone: import_zod30.z.string(),
25937
- jobs: import_zod30.z.array(RankTrackerCronJobOutput)
26248
+ trackingModes: import_zod31.z.array(RankTrackerModeSchema),
26249
+ database: import_zod31.z.string(),
26250
+ recommendedTools: import_zod31.z.array(RankTrackerToolPlanOutput),
26251
+ tables: import_zod31.z.array(RankTrackerTableOutput),
26252
+ cron: import_zod31.z.object({
26253
+ enabled: import_zod31.z.boolean(),
26254
+ cadence: import_zod31.z.string(),
26255
+ expression: import_zod31.z.string(),
26256
+ timezone: import_zod31.z.string(),
26257
+ jobs: import_zod31.z.array(RankTrackerCronJobOutput)
25938
26258
  }),
25939
- metrics: import_zod30.z.array(import_zod30.z.string()),
25940
- implementationPrompt: import_zod30.z.string()
26259
+ metrics: import_zod31.z.array(import_zod31.z.string()),
26260
+ implementationPrompt: import_zod31.z.string()
25941
26261
  };
25942
- OrganicResultOutput = import_zod30.z.object({
25943
- position: import_zod30.z.number().int(),
25944
- title: import_zod30.z.string(),
25945
- url: import_zod30.z.string(),
25946
- domain: import_zod30.z.string(),
26262
+ OrganicResultOutput = import_zod31.z.object({
26263
+ position: import_zod31.z.number().int(),
26264
+ title: import_zod31.z.string(),
26265
+ url: import_zod31.z.string(),
26266
+ domain: import_zod31.z.string(),
25947
26267
  snippet: NullableString
25948
26268
  });
25949
- AiOverviewOutput = import_zod30.z.object({
25950
- detected: import_zod30.z.boolean(),
26269
+ AiOverviewOutput = import_zod31.z.object({
26270
+ detected: import_zod31.z.boolean(),
25951
26271
  text: NullableString,
25952
26272
  shareUrl: NullableString.optional()
25953
26273
  }).nullable();
25954
- EntityIdsOutput = import_zod30.z.object({
25955
- entities: import_zod30.z.array(import_zod30.z.object({
25956
- name: import_zod30.z.string(),
25957
- kgId: import_zod30.z.string().nullable(),
25958
- cid: import_zod30.z.string().nullable(),
25959
- gcid: import_zod30.z.string().nullable()
25960
- })).describe("Each entity actually named on the page, with whichever of its kgId/cid/gcid were found \u2014 the linked view. kgIds/cids/gcids below are the same IDs as flat deduplicated lists, without the name link, kept for backward compatibility."),
25961
- kgIds: import_zod30.z.array(import_zod30.z.string()),
25962
- cids: import_zod30.z.array(import_zod30.z.string()),
25963
- gcids: import_zod30.z.array(import_zod30.z.string())
26274
+ EntityIdsOutput = import_zod31.z.object({
26275
+ entities: import_zod31.z.array(import_zod31.z.object({
26276
+ name: import_zod31.z.string(),
26277
+ kgId: import_zod31.z.string().nullable(),
26278
+ cid: import_zod31.z.string().nullable(),
26279
+ gcid: import_zod31.z.string().nullable()
26280
+ })).describe("Entities named on the page with their kgId/cid/gcid. Flat lists below are the same IDs deduplicated, kept for backward compatibility."),
26281
+ kgIds: import_zod31.z.array(import_zod31.z.string()),
26282
+ cids: import_zod31.z.array(import_zod31.z.string()),
26283
+ gcids: import_zod31.z.array(import_zod31.z.string())
25964
26284
  }).nullable();
25965
26285
  HarvestPaaOutputSchema = {
25966
- query: import_zod30.z.string(),
26286
+ query: import_zod31.z.string(),
25967
26287
  location: NullableString,
25968
- questionCount: import_zod30.z.number().int().min(0),
26288
+ questionCount: import_zod31.z.number().int().min(0),
25969
26289
  completionStatus: NullableString,
25970
- questions: import_zod30.z.array(import_zod30.z.object({
25971
- question: import_zod30.z.string(),
26290
+ questions: import_zod31.z.array(import_zod31.z.object({
26291
+ question: import_zod31.z.string(),
25972
26292
  answer: NullableString,
25973
26293
  sourceTitle: NullableString,
25974
26294
  sourceSite: NullableString
25975
26295
  })),
25976
- organicResults: import_zod30.z.array(OrganicResultOutput),
26296
+ organicResults: import_zod31.z.array(OrganicResultOutput),
25977
26297
  aiOverview: AiOverviewOutput,
25978
26298
  entityIds: EntityIdsOutput,
25979
- durationMs: import_zod30.z.number().min(0).nullable()
26299
+ durationMs: import_zod31.z.number().min(0).nullable()
25980
26300
  };
25981
26301
  SearchSerpOutputSchema = {
25982
- query: import_zod30.z.string(),
26302
+ query: import_zod31.z.string(),
25983
26303
  location: NullableString,
25984
- organicResults: import_zod30.z.array(OrganicResultOutput),
25985
- localPack: import_zod30.z.array(import_zod30.z.object({
25986
- position: import_zod30.z.number().int(),
25987
- name: import_zod30.z.string(),
26304
+ organicResults: import_zod31.z.array(OrganicResultOutput),
26305
+ localPack: import_zod31.z.array(import_zod31.z.object({
26306
+ position: import_zod31.z.number().int(),
26307
+ name: import_zod31.z.string(),
25988
26308
  rating: NullableString,
25989
26309
  reviewCount: NullableString,
25990
26310
  websiteUrl: NullableString
@@ -25993,61 +26313,61 @@ var init_mcp_tool_schemas = __esm({
25993
26313
  entityIds: EntityIdsOutput
25994
26314
  };
25995
26315
  ExtractUrlOutputSchema = {
25996
- url: import_zod30.z.string(),
26316
+ url: import_zod31.z.string(),
25997
26317
  title: NullableString,
25998
- headings: import_zod30.z.array(import_zod30.z.object({
25999
- level: import_zod30.z.number().int(),
26000
- text: import_zod30.z.string()
26318
+ headings: import_zod31.z.array(import_zod31.z.object({
26319
+ level: import_zod31.z.number().int(),
26320
+ text: import_zod31.z.string()
26001
26321
  })),
26002
- schemaBlockCount: import_zod30.z.number().int().min(0),
26322
+ schemaBlockCount: import_zod31.z.number().int().min(0),
26003
26323
  entityName: NullableString,
26004
- entityTypes: import_zod30.z.array(import_zod30.z.string()),
26005
- napScore: import_zod30.z.number().nullable(),
26006
- missingSchemaFields: import_zod30.z.array(import_zod30.z.string()),
26324
+ entityTypes: import_zod31.z.array(import_zod31.z.string()),
26325
+ napScore: import_zod31.z.number().nullable(),
26326
+ missingSchemaFields: import_zod31.z.array(import_zod31.z.string()),
26007
26327
  screenshotSaved: NullableString,
26008
- memory: import_zod30.z.object({
26009
- deposited: import_zod30.z.boolean(),
26010
- vault: import_zod30.z.string().optional(),
26011
- noteId: import_zod30.z.string().optional(),
26012
- path: import_zod30.z.string().optional(),
26013
- chunks: import_zod30.z.number().int().optional(),
26014
- fileUrl: import_zod30.z.string().optional(),
26015
- fileExpiresAt: import_zod30.z.string().optional(),
26016
- error: import_zod30.z.string().optional()
26328
+ memory: import_zod31.z.object({
26329
+ deposited: import_zod31.z.boolean(),
26330
+ vault: import_zod31.z.string().optional(),
26331
+ noteId: import_zod31.z.string().optional(),
26332
+ path: import_zod31.z.string().optional(),
26333
+ chunks: import_zod31.z.number().int().optional(),
26334
+ fileUrl: import_zod31.z.string().optional(),
26335
+ fileExpiresAt: import_zod31.z.string().optional(),
26336
+ error: import_zod31.z.string().optional()
26017
26337
  }).optional()
26018
26338
  };
26019
26339
  ExtractSiteOutputSchema = {
26020
- url: import_zod30.z.string(),
26021
- pageCount: import_zod30.z.number().int().min(0),
26022
- pages: import_zod30.z.array(import_zod30.z.object({
26023
- url: import_zod30.z.string(),
26340
+ url: import_zod31.z.string(),
26341
+ pageCount: import_zod31.z.number().int().min(0),
26342
+ pages: import_zod31.z.array(import_zod31.z.object({
26343
+ url: import_zod31.z.string(),
26024
26344
  title: NullableString,
26025
- schemaTypes: import_zod30.z.array(import_zod30.z.string())
26345
+ schemaTypes: import_zod31.z.array(import_zod31.z.string())
26026
26346
  })),
26027
- durationMs: import_zod30.z.number().min(0)
26347
+ durationMs: import_zod31.z.number().min(0)
26028
26348
  };
26029
26349
  AuditSiteOutputSchema = {
26030
- url: import_zod30.z.string(),
26031
- pageCount: import_zod30.z.number().int().min(0),
26032
- durationMs: import_zod30.z.number().min(0),
26033
- bulkFolder: import_zod30.z.string().nullable(),
26034
- issues: import_zod30.z.record(import_zod30.z.string(), import_zod30.z.number()),
26035
- images: import_zod30.z.object({
26036
- unique: import_zod30.z.number().int().min(0),
26037
- totalBytes: import_zod30.z.number().min(0),
26038
- over100kb: import_zod30.z.number().int().min(0),
26039
- legacyFormat: import_zod30.z.number().int().min(0)
26350
+ url: import_zod31.z.string(),
26351
+ pageCount: import_zod31.z.number().int().min(0),
26352
+ durationMs: import_zod31.z.number().min(0),
26353
+ bulkFolder: import_zod31.z.string().nullable(),
26354
+ issues: import_zod31.z.record(import_zod31.z.string(), import_zod31.z.number()),
26355
+ images: import_zod31.z.object({
26356
+ unique: import_zod31.z.number().int().min(0),
26357
+ totalBytes: import_zod31.z.number().min(0),
26358
+ over100kb: import_zod31.z.number().int().min(0),
26359
+ legacyFormat: import_zod31.z.number().int().min(0)
26040
26360
  }),
26041
- links: import_zod30.z.object({
26042
- internal: import_zod30.z.number().int().min(0),
26043
- external: import_zod30.z.number().int().min(0),
26044
- orphans: import_zod30.z.number().int().min(0),
26045
- brokenInternal: import_zod30.z.number().int().min(0),
26046
- externalDomains: import_zod30.z.number().int().min(0)
26361
+ links: import_zod31.z.object({
26362
+ internal: import_zod31.z.number().int().min(0),
26363
+ external: import_zod31.z.number().int().min(0),
26364
+ orphans: import_zod31.z.number().int().min(0),
26365
+ brokenInternal: import_zod31.z.number().int().min(0),
26366
+ externalDomains: import_zod31.z.number().int().min(0)
26047
26367
  })
26048
26368
  };
26049
26369
  MapsPlaceIntelOutputSchema = {
26050
- name: import_zod30.z.string(),
26370
+ name: import_zod31.z.string(),
26051
26371
  rating: NullableString,
26052
26372
  reviewCount: NullableString,
26053
26373
  category: NullableString,
@@ -26059,75 +26379,75 @@ var init_mcp_tool_schemas = __esm({
26059
26379
  kgmid: NullableString,
26060
26380
  cidDecimal: NullableString,
26061
26381
  cidUrl: NullableString,
26062
- lat: import_zod30.z.number().nullable(),
26063
- lng: import_zod30.z.number().nullable(),
26064
- reviewsStatus: import_zod30.z.string(),
26065
- reviewsCollected: import_zod30.z.number().int().min(0),
26066
- reviewTopics: import_zod30.z.array(import_zod30.z.object({
26067
- label: import_zod30.z.string(),
26068
- count: import_zod30.z.string()
26382
+ lat: import_zod31.z.number().nullable(),
26383
+ lng: import_zod31.z.number().nullable(),
26384
+ reviewsStatus: import_zod31.z.string(),
26385
+ reviewsCollected: import_zod31.z.number().int().min(0),
26386
+ reviewTopics: import_zod31.z.array(import_zod31.z.object({
26387
+ label: import_zod31.z.string(),
26388
+ count: import_zod31.z.string()
26069
26389
  }))
26070
26390
  };
26071
26391
  CreditsInfoOutputSchema = {
26072
- balanceCredits: import_zod30.z.number().nullable(),
26073
- matchedCost: import_zod30.z.object({
26074
- label: import_zod30.z.string(),
26075
- credits: import_zod30.z.number(),
26076
- unit: import_zod30.z.string(),
26392
+ balanceCredits: import_zod31.z.number().nullable(),
26393
+ matchedCost: import_zod31.z.object({
26394
+ label: import_zod31.z.string(),
26395
+ credits: import_zod31.z.number(),
26396
+ unit: import_zod31.z.string(),
26077
26397
  notes: NullableString
26078
26398
  }).nullable(),
26079
- costs: import_zod30.z.array(import_zod30.z.object({
26080
- key: import_zod30.z.string(),
26081
- label: import_zod30.z.string(),
26082
- credits: import_zod30.z.number(),
26083
- unit: import_zod30.z.string(),
26399
+ costs: import_zod31.z.array(import_zod31.z.object({
26400
+ key: import_zod31.z.string(),
26401
+ label: import_zod31.z.string(),
26402
+ credits: import_zod31.z.number(),
26403
+ unit: import_zod31.z.string(),
26084
26404
  notes: NullableString
26085
26405
  })),
26086
- ledger: import_zod30.z.array(import_zod30.z.object({
26087
- createdAt: import_zod30.z.string(),
26088
- operation: import_zod30.z.string(),
26089
- credits: import_zod30.z.number(),
26406
+ ledger: import_zod31.z.array(import_zod31.z.object({
26407
+ createdAt: import_zod31.z.string(),
26408
+ operation: import_zod31.z.string(),
26409
+ credits: import_zod31.z.number(),
26090
26410
  description: NullableString
26091
26411
  })),
26092
- concurrency: import_zod30.z.object({
26093
- currentExtraSlots: import_zod30.z.number().int().min(0),
26094
- currentLimit: import_zod30.z.number().int().min(1),
26095
- hasSubscription: import_zod30.z.boolean(),
26096
- upgrade: import_zod30.z.object({
26097
- product: import_zod30.z.string(),
26098
- priceLabel: import_zod30.z.string(),
26099
- unitAmountUsd: import_zod30.z.number(),
26100
- currency: import_zod30.z.string(),
26101
- interval: import_zod30.z.string(),
26102
- billingUrl: import_zod30.z.string().url(),
26103
- terminalCommand: import_zod30.z.string(),
26104
- terminalCommandWithApiKeyEnv: import_zod30.z.string()
26412
+ concurrency: import_zod31.z.object({
26413
+ currentExtraSlots: import_zod31.z.number().int().min(0),
26414
+ currentLimit: import_zod31.z.number().int().min(1),
26415
+ hasSubscription: import_zod31.z.boolean(),
26416
+ upgrade: import_zod31.z.object({
26417
+ product: import_zod31.z.string(),
26418
+ priceLabel: import_zod31.z.string(),
26419
+ unitAmountUsd: import_zod31.z.number(),
26420
+ currency: import_zod31.z.string(),
26421
+ interval: import_zod31.z.string(),
26422
+ billingUrl: import_zod31.z.string().url(),
26423
+ terminalCommand: import_zod31.z.string(),
26424
+ terminalCommandWithApiKeyEnv: import_zod31.z.string()
26105
26425
  })
26106
26426
  }).nullable()
26107
26427
  };
26108
26428
  MapSiteUrlsOutputSchema = {
26109
- startUrl: import_zod30.z.string(),
26110
- totalFound: import_zod30.z.number().int().min(0),
26111
- truncated: import_zod30.z.boolean(),
26112
- okCount: import_zod30.z.number().int().min(0),
26113
- redirectCount: import_zod30.z.number().int().min(0),
26114
- brokenCount: import_zod30.z.number().int().min(0),
26115
- urls: import_zod30.z.array(import_zod30.z.object({
26116
- url: import_zod30.z.string(),
26117
- status: import_zod30.z.number().int().nullable()
26429
+ startUrl: import_zod31.z.string(),
26430
+ totalFound: import_zod31.z.number().int().min(0),
26431
+ truncated: import_zod31.z.boolean(),
26432
+ okCount: import_zod31.z.number().int().min(0),
26433
+ redirectCount: import_zod31.z.number().int().min(0),
26434
+ brokenCount: import_zod31.z.number().int().min(0),
26435
+ urls: import_zod31.z.array(import_zod31.z.object({
26436
+ url: import_zod31.z.string(),
26437
+ status: import_zod31.z.number().int().nullable()
26118
26438
  })),
26119
- durationMs: import_zod30.z.number().min(0)
26439
+ durationMs: import_zod31.z.number().min(0)
26120
26440
  };
26121
26441
  YoutubeHarvestOutputSchema = {
26122
- mode: import_zod30.z.string(),
26123
- videoCount: import_zod30.z.number().int().min(0),
26124
- channel: import_zod30.z.object({
26442
+ mode: import_zod31.z.string(),
26443
+ videoCount: import_zod31.z.number().int().min(0),
26444
+ channel: import_zod31.z.object({
26125
26445
  title: NullableString,
26126
26446
  subscriberCount: NullableString
26127
26447
  }).nullable(),
26128
- videos: import_zod30.z.array(import_zod30.z.object({
26129
- videoId: import_zod30.z.string(),
26130
- title: import_zod30.z.string(),
26448
+ videos: import_zod31.z.array(import_zod31.z.object({
26449
+ videoId: import_zod31.z.string(),
26450
+ title: import_zod31.z.string(),
26131
26451
  channelName: NullableString,
26132
26452
  views: NullableString,
26133
26453
  duration: NullableString,
@@ -26135,17 +26455,33 @@ var init_mcp_tool_schemas = __esm({
26135
26455
  }))
26136
26456
  };
26137
26457
  FacebookAdSearchOutputSchema = {
26138
- query: import_zod30.z.string(),
26139
- advertiserCount: import_zod30.z.number().int().min(0),
26140
- advertisers: import_zod30.z.array(import_zod30.z.object({
26458
+ query: import_zod31.z.string(),
26459
+ advertiserCount: import_zod31.z.number().int().min(0),
26460
+ advertisers: import_zod31.z.array(import_zod31.z.object({
26141
26461
  name: NullableString,
26142
26462
  pageId: NullableString,
26143
26463
  pageUrl: NullableString,
26144
- adCount: import_zod30.z.number().int().nullable(),
26464
+ adCount: import_zod31.z.number().int().nullable(),
26145
26465
  libraryId: NullableString,
26146
26466
  sampleLibraryId: NullableString
26147
26467
  }))
26148
26468
  };
26469
+ VideoFrameAnalysisOutputSchema = {
26470
+ ok: import_zod31.z.boolean(),
26471
+ runId: NullableString,
26472
+ status: NullableString,
26473
+ message: NullableString
26474
+ };
26475
+ VideoFrameAnalysisStatusOutputSchema = {
26476
+ ok: import_zod31.z.boolean(),
26477
+ runId: NullableString,
26478
+ status: NullableString,
26479
+ progress: import_zod31.z.object({ analyzed: import_zod31.z.number().int(), total: import_zod31.z.number().int() }).nullable().optional(),
26480
+ frameCount: import_zod31.z.number().int().nullable().optional(),
26481
+ artifactPath: NullableString,
26482
+ report: NullableString,
26483
+ error: NullableString
26484
+ };
26149
26485
  RedditThreadOutputSchema = {
26150
26486
  sourceUrl: NullableString,
26151
26487
  oldRedditUrl: NullableString,
@@ -26153,21 +26489,21 @@ var init_mcp_tool_schemas = __esm({
26153
26489
  author: NullableString,
26154
26490
  score: NullableString,
26155
26491
  postBody: NullableString,
26156
- numComments: import_zod30.z.number().int().min(0),
26157
- comments: import_zod30.z.array(import_zod30.z.object({
26492
+ numComments: import_zod31.z.number().int().min(0),
26493
+ comments: import_zod31.z.array(import_zod31.z.object({
26158
26494
  author: NullableString,
26159
26495
  score: NullableString,
26160
- depth: import_zod30.z.number().int().min(0),
26161
- body: import_zod30.z.string()
26496
+ depth: import_zod31.z.number().int().min(0),
26497
+ body: import_zod31.z.string()
26162
26498
  }))
26163
26499
  };
26164
26500
  FacebookPageIntelOutputSchema = {
26165
26501
  advertiserName: NullableString,
26166
- totalAds: import_zod30.z.number().int().min(0),
26167
- activeCount: import_zod30.z.number().int().min(0),
26168
- videoCount: import_zod30.z.number().int().min(0),
26169
- imageCount: import_zod30.z.number().int().min(0),
26170
- ads: import_zod30.z.array(import_zod30.z.object({
26502
+ totalAds: import_zod31.z.number().int().min(0),
26503
+ activeCount: import_zod31.z.number().int().min(0),
26504
+ videoCount: import_zod31.z.number().int().min(0),
26505
+ imageCount: import_zod31.z.number().int().min(0),
26506
+ ads: import_zod31.z.array(import_zod31.z.object({
26171
26507
  libraryId: NullableString,
26172
26508
  status: NullableString,
26173
26509
  creativeType: NullableString,
@@ -26180,18 +26516,18 @@ var init_mcp_tool_schemas = __esm({
26180
26516
  videoUrl: NullableString,
26181
26517
  imageUrl: NullableString,
26182
26518
  videoPoster: NullableString,
26183
- variations: import_zod30.z.number().int().nullable()
26519
+ variations: import_zod31.z.number().int().nullable()
26184
26520
  }))
26185
26521
  };
26186
26522
  GoogleAdsSearchOutputSchema = {
26187
- query: import_zod30.z.string(),
26188
- region: import_zod30.z.string(),
26189
- advertiserCount: import_zod30.z.number().int().min(0),
26190
- advertisers: import_zod30.z.array(import_zod30.z.object({
26523
+ query: import_zod31.z.string(),
26524
+ region: import_zod31.z.string(),
26525
+ advertiserCount: import_zod31.z.number().int().min(0),
26526
+ advertisers: import_zod31.z.array(import_zod31.z.object({
26191
26527
  advertiserId: NullableString,
26192
26528
  name: NullableString,
26193
26529
  domain: NullableString,
26194
- approxAdCount: import_zod30.z.number().int().nullable(),
26530
+ approxAdCount: import_zod31.z.number().int().nullable(),
26195
26531
  detailUrl: NullableString
26196
26532
  }))
26197
26533
  };
@@ -26199,205 +26535,205 @@ var init_mcp_tool_schemas = __esm({
26199
26535
  advertiserId: NullableString,
26200
26536
  advertiserName: NullableString,
26201
26537
  domain: NullableString,
26202
- region: import_zod30.z.string(),
26203
- totalCreatives: import_zod30.z.number().int().min(0),
26204
- videoCount: import_zod30.z.number().int().min(0),
26205
- imageCount: import_zod30.z.number().int().min(0),
26206
- textCount: import_zod30.z.number().int().min(0),
26207
- ads: import_zod30.z.array(import_zod30.z.object({
26538
+ region: import_zod31.z.string(),
26539
+ totalCreatives: import_zod31.z.number().int().min(0),
26540
+ videoCount: import_zod31.z.number().int().min(0),
26541
+ imageCount: import_zod31.z.number().int().min(0),
26542
+ textCount: import_zod31.z.number().int().min(0),
26543
+ ads: import_zod31.z.array(import_zod31.z.object({
26208
26544
  creativeId: NullableString,
26209
26545
  advertiserId: NullableString,
26210
26546
  format: NullableString,
26211
26547
  lastShown: NullableString,
26212
26548
  detailUrl: NullableString,
26213
26549
  landingDomain: NullableString,
26214
- imageUrls: import_zod30.z.array(import_zod30.z.string()),
26550
+ imageUrls: import_zod31.z.array(import_zod31.z.string()),
26215
26551
  youtubeVideoId: NullableString,
26216
26552
  videoUrl: NullableString,
26217
- variations: import_zod30.z.number().int().nullable()
26553
+ variations: import_zod31.z.number().int().nullable()
26218
26554
  }))
26219
26555
  };
26220
26556
  FacebookVideoTranscribeOutputSchema = {
26221
- sourceUrl: import_zod30.z.string().url(),
26222
- pageUrl: import_zod30.z.string().url(),
26557
+ sourceUrl: import_zod31.z.string().url(),
26558
+ pageUrl: import_zod31.z.string().url(),
26223
26559
  videoId: NullableString,
26224
26560
  ownerName: NullableString,
26225
- selectedQuality: import_zod30.z.string(),
26226
- bitrate: import_zod30.z.number().int().nullable(),
26227
- videoDurationSec: import_zod30.z.number().nullable(),
26228
- videoUrl: import_zod30.z.string().url(),
26229
- wordCount: import_zod30.z.number().int().min(0),
26230
- chunkCount: import_zod30.z.number().int().min(0),
26231
- transcriptText: import_zod30.z.string(),
26232
- chunks: import_zod30.z.array(import_zod30.z.object({
26233
- startSec: import_zod30.z.number(),
26234
- endSec: import_zod30.z.number(),
26235
- text: import_zod30.z.string()
26561
+ selectedQuality: import_zod31.z.string(),
26562
+ bitrate: import_zod31.z.number().int().nullable(),
26563
+ videoDurationSec: import_zod31.z.number().nullable(),
26564
+ videoUrl: import_zod31.z.string().url(),
26565
+ wordCount: import_zod31.z.number().int().min(0),
26566
+ chunkCount: import_zod31.z.number().int().min(0),
26567
+ transcriptText: import_zod31.z.string(),
26568
+ chunks: import_zod31.z.array(import_zod31.z.object({
26569
+ startSec: import_zod31.z.number(),
26570
+ endSec: import_zod31.z.number(),
26571
+ text: import_zod31.z.string()
26236
26572
  }))
26237
26573
  };
26238
- TranscriptChunkOutput = import_zod30.z.object({
26239
- startSec: import_zod30.z.number(),
26240
- endSec: import_zod30.z.number(),
26241
- text: import_zod30.z.string()
26242
- });
26243
- InstagramBrowserOutput = import_zod30.z.object({
26244
- mode: import_zod30.z.literal("hosted"),
26245
- requestedMode: import_zod30.z.literal("hosted"),
26574
+ TranscriptChunkOutput = import_zod31.z.object({
26575
+ startSec: import_zod31.z.number(),
26576
+ endSec: import_zod31.z.number(),
26577
+ text: import_zod31.z.string()
26578
+ });
26579
+ InstagramBrowserOutput = import_zod31.z.object({
26580
+ mode: import_zod31.z.literal("hosted"),
26581
+ requestedMode: import_zod31.z.literal("hosted"),
26246
26582
  profileName: NullableString,
26247
- profileSource: import_zod30.z.literal("hosted"),
26248
- profileDirConfigured: import_zod30.z.boolean(),
26249
- executablePathConfigured: import_zod30.z.boolean()
26250
- });
26251
- InstagramPaginationOutput = import_zod30.z.object({
26252
- maxItems: import_zod30.z.number().int().min(1).max(2e3),
26253
- maxScrolls: import_zod30.z.number().int().min(0).max(250),
26254
- attemptedScrolls: import_zod30.z.number().int().min(0),
26255
- stableScrolls: import_zod30.z.number().int().min(0),
26256
- stableScrollLimit: import_zod30.z.number().int().min(1).max(10),
26257
- scrollDelayMs: import_zod30.z.number().int().min(250).max(5e3),
26258
- reachedMaxItems: import_zod30.z.boolean(),
26259
- reachedReportedPostCount: import_zod30.z.boolean(),
26260
- finalScrollHeight: import_zod30.z.number().int().nullable(),
26261
- stoppedReason: import_zod30.z.enum(["max_items", "reported_post_count", "stable_scrolls", "max_scrolls", "no_scrolls"]),
26262
- stages: import_zod30.z.array(import_zod30.z.object({
26263
- stage: import_zod30.z.string(),
26264
- itemCount: import_zod30.z.number().int().min(0),
26265
- addedCount: import_zod30.z.number().int().min(0),
26266
- scrollY: import_zod30.z.number().nullable(),
26267
- scrollHeight: import_zod30.z.number().nullable()
26583
+ profileSource: import_zod31.z.literal("hosted"),
26584
+ profileDirConfigured: import_zod31.z.boolean(),
26585
+ executablePathConfigured: import_zod31.z.boolean()
26586
+ });
26587
+ InstagramPaginationOutput = import_zod31.z.object({
26588
+ maxItems: import_zod31.z.number().int().min(1).max(2e3),
26589
+ maxScrolls: import_zod31.z.number().int().min(0).max(250),
26590
+ attemptedScrolls: import_zod31.z.number().int().min(0),
26591
+ stableScrolls: import_zod31.z.number().int().min(0),
26592
+ stableScrollLimit: import_zod31.z.number().int().min(1).max(10),
26593
+ scrollDelayMs: import_zod31.z.number().int().min(250).max(5e3),
26594
+ reachedMaxItems: import_zod31.z.boolean(),
26595
+ reachedReportedPostCount: import_zod31.z.boolean(),
26596
+ finalScrollHeight: import_zod31.z.number().int().nullable(),
26597
+ stoppedReason: import_zod31.z.enum(["max_items", "reported_post_count", "stable_scrolls", "max_scrolls", "no_scrolls"]),
26598
+ stages: import_zod31.z.array(import_zod31.z.object({
26599
+ stage: import_zod31.z.string(),
26600
+ itemCount: import_zod31.z.number().int().min(0),
26601
+ addedCount: import_zod31.z.number().int().min(0),
26602
+ scrollY: import_zod31.z.number().nullable(),
26603
+ scrollHeight: import_zod31.z.number().nullable()
26268
26604
  }))
26269
26605
  });
26270
26606
  InstagramProfileContentOutputSchema = {
26271
- handle: import_zod30.z.string(),
26272
- profileUrl: import_zod30.z.string().url(),
26273
- pageUrl: import_zod30.z.string().url(),
26607
+ handle: import_zod31.z.string(),
26608
+ profileUrl: import_zod31.z.string().url(),
26609
+ pageUrl: import_zod31.z.string().url(),
26274
26610
  browser: InstagramBrowserOutput,
26275
26611
  profileName: NullableString,
26276
- reportedPostCount: import_zod30.z.number().int().nullable(),
26612
+ reportedPostCount: import_zod31.z.number().int().nullable(),
26277
26613
  reportedPostCountText: NullableString,
26278
26614
  followerCountText: NullableString,
26279
26615
  followingCountText: NullableString,
26280
- collectedContentCount: import_zod30.z.number().int().min(0),
26281
- typeCounts: import_zod30.z.object({
26282
- post: import_zod30.z.number().int().min(0),
26283
- reel: import_zod30.z.number().int().min(0),
26284
- tv: import_zod30.z.number().int().min(0)
26616
+ collectedContentCount: import_zod31.z.number().int().min(0),
26617
+ typeCounts: import_zod31.z.object({
26618
+ post: import_zod31.z.number().int().min(0),
26619
+ reel: import_zod31.z.number().int().min(0),
26620
+ tv: import_zod31.z.number().int().min(0)
26285
26621
  }),
26286
26622
  pagination: InstagramPaginationOutput,
26287
- limited: import_zod30.z.boolean(),
26288
- limitations: import_zod30.z.array(import_zod30.z.string()),
26289
- items: import_zod30.z.array(import_zod30.z.object({
26290
- url: import_zod30.z.string().url(),
26291
- type: import_zod30.z.enum(["post", "reel", "tv"]),
26292
- shortcode: import_zod30.z.string(),
26623
+ limited: import_zod31.z.boolean(),
26624
+ limitations: import_zod31.z.array(import_zod31.z.string()),
26625
+ items: import_zod31.z.array(import_zod31.z.object({
26626
+ url: import_zod31.z.string().url(),
26627
+ type: import_zod31.z.enum(["post", "reel", "tv"]),
26628
+ shortcode: import_zod31.z.string(),
26293
26629
  anchorText: NullableString,
26294
- firstSeenStage: import_zod30.z.string()
26630
+ firstSeenStage: import_zod31.z.string()
26295
26631
  }))
26296
26632
  };
26297
- InstagramMediaTrackOutput = import_zod30.z.object({
26298
- url: import_zod30.z.string().url(),
26299
- streamType: import_zod30.z.enum(["video", "audio", "unknown"]),
26300
- bitrate: import_zod30.z.number().int().nullable(),
26301
- durationSec: import_zod30.z.number().nullable(),
26633
+ InstagramMediaTrackOutput = import_zod31.z.object({
26634
+ url: import_zod31.z.string().url(),
26635
+ streamType: import_zod31.z.enum(["video", "audio", "unknown"]),
26636
+ bitrate: import_zod31.z.number().int().nullable(),
26637
+ durationSec: import_zod31.z.number().nullable(),
26302
26638
  vencodeTag: NullableString,
26303
- width: import_zod30.z.number().int().nullable(),
26304
- height: import_zod30.z.number().int().nullable()
26639
+ width: import_zod31.z.number().int().nullable(),
26640
+ height: import_zod31.z.number().int().nullable()
26305
26641
  });
26306
- InstagramDownloadOutput = import_zod30.z.object({
26307
- kind: import_zod30.z.enum(["text", "image", "video", "audio", "muxed_video"]),
26308
- url: import_zod30.z.string().url().nullable(),
26642
+ InstagramDownloadOutput = import_zod31.z.object({
26643
+ kind: import_zod31.z.enum(["text", "image", "video", "audio", "muxed_video"]),
26644
+ url: import_zod31.z.string().url().nullable(),
26309
26645
  savedPath: NullableString,
26310
- sizeBytes: import_zod30.z.number().int().nullable(),
26646
+ sizeBytes: import_zod31.z.number().int().nullable(),
26311
26647
  mimeType: NullableString,
26312
26648
  error: NullableString
26313
26649
  });
26314
26650
  InstagramMediaDownloadOutputSchema = {
26315
- sourceUrl: import_zod30.z.string().url(),
26316
- pageUrl: import_zod30.z.string().url(),
26651
+ sourceUrl: import_zod31.z.string().url(),
26652
+ pageUrl: import_zod31.z.string().url(),
26317
26653
  browser: InstagramBrowserOutput,
26318
- type: import_zod30.z.enum(["post", "reel", "tv"]).nullable(),
26654
+ type: import_zod31.z.enum(["post", "reel", "tv"]).nullable(),
26319
26655
  shortcode: NullableString,
26320
26656
  ownerName: NullableString,
26321
26657
  caption: NullableString,
26322
- imageUrl: import_zod30.z.string().url().nullable(),
26323
- trackCount: import_zod30.z.number().int().min(0),
26658
+ imageUrl: import_zod31.z.string().url().nullable(),
26659
+ trackCount: import_zod31.z.number().int().min(0),
26324
26660
  selectedVideoTrack: InstagramMediaTrackOutput.nullable(),
26325
26661
  selectedAudioTrack: InstagramMediaTrackOutput.nullable(),
26326
- downloads: import_zod30.z.array(InstagramDownloadOutput),
26662
+ downloads: import_zod31.z.array(InstagramDownloadOutput),
26327
26663
  outputDir: NullableString,
26328
- warnings: import_zod30.z.array(import_zod30.z.string()),
26329
- limitations: import_zod30.z.array(import_zod30.z.string()),
26330
- transcript: import_zod30.z.object({
26331
- wordCount: import_zod30.z.number().int().min(0),
26332
- chunkCount: import_zod30.z.number().int().min(0),
26333
- durationMs: import_zod30.z.number().nullable(),
26334
- transcriptText: import_zod30.z.string(),
26335
- chunks: import_zod30.z.array(TranscriptChunkOutput)
26664
+ warnings: import_zod31.z.array(import_zod31.z.string()),
26665
+ limitations: import_zod31.z.array(import_zod31.z.string()),
26666
+ transcript: import_zod31.z.object({
26667
+ wordCount: import_zod31.z.number().int().min(0),
26668
+ chunkCount: import_zod31.z.number().int().min(0),
26669
+ durationMs: import_zod31.z.number().nullable(),
26670
+ transcriptText: import_zod31.z.string(),
26671
+ chunks: import_zod31.z.array(TranscriptChunkOutput)
26336
26672
  }).nullable()
26337
26673
  };
26338
26674
  YoutubeTranscribeOutputSchema = {
26339
26675
  videoId: NullableString,
26340
26676
  url: NullableString,
26341
- wordCount: import_zod30.z.number().int().min(0),
26342
- chunkCount: import_zod30.z.number().int().min(0),
26343
- durationMs: import_zod30.z.number().nullable(),
26344
- transcriptText: import_zod30.z.string(),
26345
- chunks: import_zod30.z.array(TranscriptChunkOutput),
26346
- resolvedInputs: import_zod30.z.object({
26677
+ wordCount: import_zod31.z.number().int().min(0),
26678
+ chunkCount: import_zod31.z.number().int().min(0),
26679
+ durationMs: import_zod31.z.number().nullable(),
26680
+ transcriptText: import_zod31.z.string(),
26681
+ chunks: import_zod31.z.array(TranscriptChunkOutput),
26682
+ resolvedInputs: import_zod31.z.object({
26347
26683
  videoId: NullableString,
26348
26684
  url: NullableString
26349
26685
  })
26350
26686
  };
26351
26687
  FacebookAdTranscribeOutputSchema = {
26352
- videoUrl: import_zod30.z.string().url(),
26353
- wordCount: import_zod30.z.number().int().min(0),
26354
- chunkCount: import_zod30.z.number().int().min(0),
26355
- durationMs: import_zod30.z.number().nullable(),
26356
- transcriptText: import_zod30.z.string(),
26357
- chunks: import_zod30.z.array(TranscriptChunkOutput),
26358
- resolvedInputs: import_zod30.z.object({
26359
- videoUrl: import_zod30.z.string().url()
26688
+ videoUrl: import_zod31.z.string().url(),
26689
+ wordCount: import_zod31.z.number().int().min(0),
26690
+ chunkCount: import_zod31.z.number().int().min(0),
26691
+ durationMs: import_zod31.z.number().nullable(),
26692
+ transcriptText: import_zod31.z.string(),
26693
+ chunks: import_zod31.z.array(TranscriptChunkOutput),
26694
+ resolvedInputs: import_zod31.z.object({
26695
+ videoUrl: import_zod31.z.string().url()
26360
26696
  })
26361
26697
  };
26362
26698
  GoogleAdsTranscribeOutputSchema = {
26363
- videoUrl: import_zod30.z.string().url(),
26364
- wordCount: import_zod30.z.number().int().min(0),
26365
- chunkCount: import_zod30.z.number().int().min(0),
26366
- durationMs: import_zod30.z.number().nullable(),
26367
- transcriptText: import_zod30.z.string(),
26368
- chunks: import_zod30.z.array(TranscriptChunkOutput),
26369
- resolvedInputs: import_zod30.z.object({
26370
- videoUrl: import_zod30.z.string().url()
26699
+ videoUrl: import_zod31.z.string().url(),
26700
+ wordCount: import_zod31.z.number().int().min(0),
26701
+ chunkCount: import_zod31.z.number().int().min(0),
26702
+ durationMs: import_zod31.z.number().nullable(),
26703
+ transcriptText: import_zod31.z.string(),
26704
+ chunks: import_zod31.z.array(TranscriptChunkOutput),
26705
+ resolvedInputs: import_zod31.z.object({
26706
+ videoUrl: import_zod31.z.string().url()
26371
26707
  })
26372
26708
  };
26373
26709
  CaptureSerpSnapshotOutputSchema = {
26374
- schemaVersion: import_zod30.z.literal("serp-intelligence.capture.v1"),
26375
- status: import_zod30.z.string(),
26710
+ schemaVersion: import_zod31.z.literal("serp-intelligence.capture.v1"),
26711
+ status: import_zod31.z.string(),
26376
26712
  query: NullableString,
26377
26713
  location: NullableString,
26378
26714
  capturedAt: NullableString,
26379
- resultCount: import_zod30.z.number().int().min(0).nullable(),
26715
+ resultCount: import_zod31.z.number().int().min(0).nullable(),
26380
26716
  snapshotId: NullableString,
26381
- resolvedInputs: import_zod30.z.record(import_zod30.z.unknown()),
26382
- artifacts: import_zod30.z.array(import_zod30.z.record(import_zod30.z.unknown())),
26383
- diagnostics: import_zod30.z.record(import_zod30.z.unknown()).nullable(),
26384
- providerPayload: import_zod30.z.record(import_zod30.z.unknown())
26717
+ resolvedInputs: import_zod31.z.record(import_zod31.z.unknown()),
26718
+ artifacts: import_zod31.z.array(import_zod31.z.record(import_zod31.z.unknown())),
26719
+ diagnostics: import_zod31.z.record(import_zod31.z.unknown()).nullable(),
26720
+ providerPayload: import_zod31.z.record(import_zod31.z.unknown())
26385
26721
  };
26386
26722
  CaptureSerpPageSnapshotsOutputSchema = {
26387
- schemaVersion: import_zod30.z.literal("serp-intelligence.page-snapshots.v1"),
26388
- status: import_zod30.z.string(),
26389
- count: import_zod30.z.number().int().min(0),
26390
- failedCount: import_zod30.z.number().int().min(0),
26391
- captures: import_zod30.z.array(import_zod30.z.record(import_zod30.z.unknown())),
26392
- resolvedInputs: import_zod30.z.record(import_zod30.z.unknown()),
26393
- diagnostics: import_zod30.z.record(import_zod30.z.unknown()).nullable(),
26394
- providerPayload: import_zod30.z.record(import_zod30.z.unknown())
26723
+ schemaVersion: import_zod31.z.literal("serp-intelligence.page-snapshots.v1"),
26724
+ status: import_zod31.z.string(),
26725
+ count: import_zod31.z.number().int().min(0),
26726
+ failedCount: import_zod31.z.number().int().min(0),
26727
+ captures: import_zod31.z.array(import_zod31.z.record(import_zod31.z.unknown())),
26728
+ resolvedInputs: import_zod31.z.record(import_zod31.z.unknown()),
26729
+ diagnostics: import_zod31.z.record(import_zod31.z.unknown()).nullable(),
26730
+ providerPayload: import_zod31.z.record(import_zod31.z.unknown())
26395
26731
  };
26396
26732
  CreditsInfoInputSchema = {
26397
- item: import_zod30.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
26398
- includeLedger: import_zod30.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
26733
+ item: import_zod31.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
26734
+ includeLedger: import_zod31.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
26399
26735
  };
26400
- WorkflowIdSchema2 = import_zod30.z.enum([
26736
+ WorkflowIdSchema2 = import_zod31.z.enum([
26401
26737
  "directory",
26402
26738
  "get-leads",
26403
26739
  "agent-packet",
@@ -26408,129 +26744,129 @@ var init_mcp_tool_schemas = __esm({
26408
26744
  "ai-overview-language"
26409
26745
  ]);
26410
26746
  WorkflowListInputSchema = {
26411
- includeRecipes: import_zod30.z.boolean().default(true).describe("Include high-level AI-facing recipes such as market analysis, ICP research, forum/review research, brand design briefings, CRO audits, positioning briefs, content gaps, and AI search visibility audits.")
26747
+ includeRecipes: import_zod31.z.boolean().default(true).describe("Include high-level AI-facing recipes (market analysis, ICP research, CRO audits, content gaps, etc).")
26412
26748
  };
26413
26749
  WorkflowSuggestInputSchema = {
26414
- goal: import_zod30.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
26415
- query: import_zod30.z.string().optional().describe("Business category, niche, or Maps query when known."),
26416
- keyword: import_zod30.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
26417
- domain: import_zod30.z.string().optional().describe("Target domain or brand domain when known."),
26418
- url: import_zod30.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
26419
- location: import_zod30.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
26420
- state: import_zod30.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
26421
- maxSuggestions: import_zod30.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
26750
+ goal: import_zod31.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
26751
+ query: import_zod31.z.string().optional().describe("Business category, niche, or Maps query when known."),
26752
+ keyword: import_zod31.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
26753
+ domain: import_zod31.z.string().optional().describe("Target domain or brand domain when known."),
26754
+ url: import_zod31.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
26755
+ location: import_zod31.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
26756
+ state: import_zod31.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
26757
+ maxSuggestions: import_zod31.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
26422
26758
  };
26423
26759
  WorkflowRunInputSchema = {
26424
- workflowId: WorkflowIdSchema2.describe("Workflow to run: directory, get-leads, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. Use only these values; call workflow_list or workflow_suggest first when unsure."),
26425
- input: import_zod30.z.record(import_zod30.z.unknown()).default({}).describe("Workflow-specific input object. Examples: get-leads uses {query, location, maxResults?, enrichWebsites?, hydrateReviewCounts?}; agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
26426
- webhookUrl: import_zod30.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
26760
+ workflowId: WorkflowIdSchema2.describe("Workflow to run. Call workflow_list or workflow_suggest first when unsure."),
26761
+ input: import_zod31.z.record(import_zod31.z.unknown()).default({}).describe("Workflow-specific input object; shape depends on workflowId. Call workflow_list or workflow_suggest to see required fields."),
26762
+ webhookUrl: import_zod31.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
26427
26763
  };
26428
26764
  WorkflowStepInputSchema = {
26429
- runId: import_zod30.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself. Advances the run by exactly one step (one logical leg, e.g. one live harvest).")
26765
+ runId: import_zod31.z.string().min(1).describe("Workflow run id returned by workflow_run/workflow_step/workflow_status. Advances the run by exactly one step.")
26430
26766
  };
26431
26767
  WorkflowStatusInputSchema = {
26432
- runId: import_zod30.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself.")
26768
+ runId: import_zod31.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself.")
26433
26769
  };
26434
26770
  WorkflowArtifactReadInputSchema = {
26435
- runId: import_zod30.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself."),
26436
- artifactId: import_zod30.z.string().min(1).describe("Artifact id from the run artifact list returned by workflow_run, workflow_step, or workflow_status. Use only a returned artifactId; do not construct one yourself."),
26437
- maxBytes: import_zod30.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline. Use lower values for large CSV/JSON artifacts; call again with the downloadUrl if needed outside MCP.")
26771
+ runId: import_zod31.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself."),
26772
+ artifactId: import_zod31.z.string().min(1).describe("Artifact id from the run artifact list returned by workflow_run, workflow_step, or workflow_status. Use only a returned artifactId; do not construct one yourself."),
26773
+ maxBytes: import_zod31.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.")
26438
26774
  };
26439
- WorkflowRecipeOutput = import_zod30.z.object({
26440
- id: import_zod30.z.string(),
26441
- title: import_zod30.z.string(),
26442
- description: import_zod30.z.string(),
26443
- primaryWorkflowId: import_zod30.z.string().nullable(),
26444
- recommendedTools: import_zod30.z.array(import_zod30.z.string()),
26445
- requiredInputs: import_zod30.z.array(import_zod30.z.string()),
26446
- optionalInputs: import_zod30.z.array(import_zod30.z.string()),
26447
- produces: import_zod30.z.array(import_zod30.z.string()),
26448
- runHint: import_zod30.z.string()
26449
- });
26450
- WorkflowDefinitionOutput = import_zod30.z.object({
26451
- id: import_zod30.z.string(),
26452
- title: import_zod30.z.string(),
26453
- description: import_zod30.z.string()
26775
+ WorkflowRecipeOutput = import_zod31.z.object({
26776
+ id: import_zod31.z.string(),
26777
+ title: import_zod31.z.string(),
26778
+ description: import_zod31.z.string(),
26779
+ primaryWorkflowId: import_zod31.z.string().nullable(),
26780
+ recommendedTools: import_zod31.z.array(import_zod31.z.string()),
26781
+ requiredInputs: import_zod31.z.array(import_zod31.z.string()),
26782
+ optionalInputs: import_zod31.z.array(import_zod31.z.string()),
26783
+ produces: import_zod31.z.array(import_zod31.z.string()),
26784
+ runHint: import_zod31.z.string()
26785
+ });
26786
+ WorkflowDefinitionOutput = import_zod31.z.object({
26787
+ id: import_zod31.z.string(),
26788
+ title: import_zod31.z.string(),
26789
+ description: import_zod31.z.string()
26454
26790
  });
26455
- WorkflowArtifactOutput = import_zod30.z.record(import_zod30.z.unknown());
26791
+ WorkflowArtifactOutput = import_zod31.z.record(import_zod31.z.unknown());
26456
26792
  WorkflowListOutputSchema = {
26457
- workflows: import_zod30.z.array(WorkflowDefinitionOutput),
26458
- recipes: import_zod30.z.array(WorkflowRecipeOutput)
26793
+ workflows: import_zod31.z.array(WorkflowDefinitionOutput),
26794
+ recipes: import_zod31.z.array(WorkflowRecipeOutput)
26459
26795
  };
26460
26796
  WorkflowSuggestOutputSchema = {
26461
- goal: import_zod30.z.string(),
26462
- suggestions: import_zod30.z.array(WorkflowRecipeOutput)
26797
+ goal: import_zod31.z.string(),
26798
+ suggestions: import_zod31.z.array(WorkflowRecipeOutput)
26463
26799
  };
26464
26800
  WorkflowRunOutputSchema = {
26465
- workflowId: import_zod30.z.string(),
26466
- input: import_zod30.z.record(import_zod30.z.unknown()),
26467
- run: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26468
- summary: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26469
- step: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26470
- nextStep: import_zod30.z.record(import_zod30.z.unknown()).nullable().optional(),
26471
- done: import_zod30.z.boolean().optional(),
26472
- artifacts: import_zod30.z.array(WorkflowArtifactOutput)
26801
+ workflowId: import_zod31.z.string(),
26802
+ input: import_zod31.z.record(import_zod31.z.unknown()),
26803
+ run: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26804
+ summary: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26805
+ step: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26806
+ nextStep: import_zod31.z.record(import_zod31.z.unknown()).nullable().optional(),
26807
+ done: import_zod31.z.boolean().optional(),
26808
+ artifacts: import_zod31.z.array(WorkflowArtifactOutput)
26473
26809
  };
26474
26810
  WorkflowStepOutputSchema = {
26475
- runId: import_zod30.z.string(),
26476
- run: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26477
- summary: import_zod30.z.record(import_zod30.z.unknown()).nullable().optional(),
26478
- step: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26479
- nextStep: import_zod30.z.record(import_zod30.z.unknown()).nullable().optional(),
26480
- done: import_zod30.z.boolean(),
26481
- artifacts: import_zod30.z.array(WorkflowArtifactOutput)
26811
+ runId: import_zod31.z.string(),
26812
+ run: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26813
+ summary: import_zod31.z.record(import_zod31.z.unknown()).nullable().optional(),
26814
+ step: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26815
+ nextStep: import_zod31.z.record(import_zod31.z.unknown()).nullable().optional(),
26816
+ done: import_zod31.z.boolean(),
26817
+ artifacts: import_zod31.z.array(WorkflowArtifactOutput)
26482
26818
  };
26483
26819
  WorkflowStatusOutputSchema = {
26484
- run: import_zod30.z.record(import_zod30.z.unknown()).optional(),
26485
- artifacts: import_zod30.z.array(WorkflowArtifactOutput)
26820
+ run: import_zod31.z.record(import_zod31.z.unknown()).optional(),
26821
+ artifacts: import_zod31.z.array(WorkflowArtifactOutput)
26486
26822
  };
26487
26823
  WorkflowArtifactReadOutputSchema = {
26488
- runId: import_zod30.z.string(),
26489
- artifactId: import_zod30.z.string(),
26490
- contentType: import_zod30.z.string(),
26491
- bytes: import_zod30.z.number().int().min(0),
26492
- truncated: import_zod30.z.boolean(),
26493
- text: import_zod30.z.string()
26824
+ runId: import_zod31.z.string(),
26825
+ artifactId: import_zod31.z.string(),
26826
+ contentType: import_zod31.z.string(),
26827
+ bytes: import_zod31.z.number().int().min(0),
26828
+ truncated: import_zod31.z.boolean(),
26829
+ text: import_zod31.z.string()
26494
26830
  };
26495
26831
  SearchSerpInputSchema = {
26496
- query: import_zod30.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
26497
- location: import_zod30.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
26498
- gl: import_zod30.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
26499
- hl: import_zod30.z.string().default("en").describe("Google interface/content language inferred from user request."),
26500
- device: import_zod30.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
26501
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force local-pack targeting.'),
26502
- proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
26503
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
26504
- pages: import_zod30.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
26832
+ query: import_zod31.z.string().min(1).describe('The search query. KEEP the place in the query text for localized results (e.g. "best dentist Brooklyn NY") and also set location \u2014 city-in-query is what localizes reliably.'),
26833
+ location: import_zod31.z.string().optional().describe("City, region, or country for geo signals. Set alongside city-in-query wording; alone it does NOT reliably localize."),
26834
+ gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
26835
+ hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from user request."),
26836
+ device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
26837
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP for rank-tracking fidelity, is frequently CAPTCHA-blocked, and accepts failures.'),
26838
+ proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
26839
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior."),
26840
+ pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132).")
26505
26841
  };
26506
26842
  CaptureSerpSnapshotInputSchema = {
26507
- query: import_zod30.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
26508
- location: import_zod30.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
26509
- gl: import_zod30.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
26510
- hl: import_zod30.z.string().default("en").describe("Google interface/content language inferred from the user request."),
26511
- device: import_zod30.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
26512
- proxyMode: import_zod30.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force city/ZIP targeting.'),
26513
- proxyZip: import_zod30.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when a precise city-center or ZIP proxy is needed."),
26514
- pages: import_zod30.z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
26515
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
26516
- includePageSnapshots: import_zod30.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
26517
- pageSnapshotLimit: import_zod30.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
26843
+ query: import_zod31.z.string().min(1).describe('Search query to capture. KEEP the place in the query text for localized captures (e.g. "botox clinic austin tx") and also set location.'),
26844
+ location: import_zod31.z.string().optional().describe("City, region, country, or service area for localized Google results."),
26845
+ gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from the requested market."),
26846
+ hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from the user request."),
26847
+ device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings/evidence."),
26848
+ proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP, is frequently CAPTCHA-blocked, and accepts failures.'),
26849
+ proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
26850
+ pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("Google result pages to capture. Use 2 only for deeper ranking evidence."),
26851
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy/location diagnostics."),
26852
+ includePageSnapshots: import_zod31.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs."),
26853
+ pageSnapshotLimit: import_zod31.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots when includePageSnapshots is true.")
26518
26854
  };
26519
26855
  ScreenshotInputSchema = {
26520
- url: import_zod30.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
26521
- device: import_zod30.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
26522
- allowLocal: import_zod30.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
26856
+ url: import_zod31.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
26857
+ device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
26858
+ allowLocal: import_zod31.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
26523
26859
  };
26524
26860
  CaptureSerpPageSnapshotsInputSchema = {
26525
- urls: import_zod30.z.array(import_zod30.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
26526
- targets: import_zod30.z.array(import_zod30.z.object({
26527
- url: import_zod30.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
26528
- sourceKind: import_zod30.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
26529
- sourcePosition: import_zod30.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
26530
- }).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
26531
- maxConcurrency: import_zod30.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
26532
- timeoutMs: import_zod30.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
26533
- debug: import_zod30.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
26861
+ urls: import_zod31.z.array(import_zod31.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
26862
+ targets: import_zod31.z.array(import_zod31.z.object({
26863
+ url: import_zod31.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
26864
+ sourceKind: import_zod31.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured."),
26865
+ sourcePosition: import_zod31.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
26866
+ }).strict()).min(1).max(25).optional().describe("Structured targets. Use instead of urls when source kind or position should be preserved."),
26867
+ maxConcurrency: import_zod31.z.number().int().min(1).max(5).default(2).describe("Parallel page captures."),
26868
+ timeoutMs: import_zod31.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds; timeouts return as structured capture failures."),
26869
+ debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
26534
26870
  };
26535
26871
  }
26536
26872
  });
@@ -26877,14 +27213,14 @@ function liveWebToolAnnotations(title) {
26877
27213
  function registerSerpIntelligenceCaptureTools(server, executor) {
26878
27214
  server.registerTool("capture_serp_snapshot", {
26879
27215
  title: "SERP Intelligence Snapshot",
26880
- description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location and infer gl/hl. Routing is automatic \u2014 leave proxyMode unset. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
27216
+ description: "Capture a structured SERP Intelligence Google snapshot (the product capture path used by Phoenix). Split query from location; leave proxyMode unset.",
26881
27217
  inputSchema: CaptureSerpSnapshotInputSchema,
26882
27218
  outputSchema: CaptureSerpSnapshotOutputSchema,
26883
27219
  annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
26884
27220
  }, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
26885
27221
  server.registerTool("capture_serp_page_snapshots", {
26886
27222
  title: "SERP Intelligence Page Snapshots",
26887
- description: "Capture public ranking-page evidence through POST /serp-intelligence/page-snapshots, the same product page snapshot path used by Phoenix. Provide urls for simple captures or targets when preserving organic, AI citation, local-pack, configured target, or site-subject source metadata. Private IPs, localhost, file URLs, and internal URLs are rejected by the service. Use timeoutMs for slow pages and debug true for sanitized proxy/browser diagnostics.",
27223
+ description: "Capture public ranking-page evidence as SERP Intelligence page snapshots (the product path used by Phoenix). Provide urls, or targets to preserve source metadata. Private IPs, localhost, file, and internal URLs are rejected.",
26888
27224
  inputSchema: CaptureSerpPageSnapshotsInputSchema,
26889
27225
  outputSchema: CaptureSerpPageSnapshotsOutputSchema,
26890
27226
  annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
@@ -26945,203 +27281,217 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
26945
27281
  if (savesReports) registerSavedReportResources(server);
26946
27282
  server.registerTool("harvest_paa", {
26947
27283
  title: "Google PAA + SERP Harvest",
26948
- description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en"). Routing is automatic \u2014 leave proxyMode unset. If a deep harvest exceeds the time budget it returns the questions gathered so far (partial), billed per returned question. Use maxQuestions 30 normally, 100-200 for "full", "deep", "all", or comprehensive research. Deep harvests above 100 questions can run for several minutes with no interim progress \u2014 warn the user before starting one and keep maxQuestions at or below 100 unless they explicitly want a deep harvest. Credits are charged by extracted question; unused request hold is refunded.'),
27284
+ description: withReportNote("Best default tool for Google search research: People Also Ask questions with answers/sources, organic SERP, local pack, entity IDs, and AI Overview. Split topic from location; leave proxyMode unset. Warn the user before maxQuestions above 100 \u2014 deep harvests can run several minutes with no interim progress, billed per extracted question."),
26949
27285
  inputSchema: HarvestPaaInputSchema,
26950
27286
  outputSchema: HarvestPaaOutputSchema,
26951
27287
  annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
26952
27288
  }, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
26953
27289
  server.registerTool("search_serp", {
26954
27290
  title: "Google SERP Lookup",
26955
- description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request. Routing is automatic \u2014 leave proxyMode unset; set location only to force local-pack targeting."),
27291
+ description: withReportNote("Fast Google SERP lookup without PAA expansion \u2014 rankings, organic results, local pack, positions. Split topic from location; leave proxyMode unset."),
26956
27292
  inputSchema: SearchSerpInputSchema,
26957
27293
  outputSchema: SearchSerpOutputSchema,
26958
27294
  annotations: liveWebToolAnnotations("Google SERP Lookup")
26959
27295
  }, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
26960
27296
  server.registerTool("extract_url", {
26961
27297
  title: "Single URL Extract",
26962
- description: withReportNote("Extract structured data from one public URL when the user provides one page, asks to inspect/scrape a page, or needs page content, schema, headings, metadata, screenshots, branding, or media assets. Returns structured page fields plus artifact handles for saved reports/screenshots/media when requested. Set depositToVault:true to also save the full page into the user's MCP Memory vault server-side (the full body is NOT returned to chat). Use map_site_urls before extracting a site inventory; use extract_site for multi-page crawling."),
27298
+ description: withReportNote("Extract structured data from one public URL: content, schema, headings, metadata, screenshots, branding, or media assets. Set depositToVault:true to save the full page into the user's MCP Memory vault server-side (not returned to chat)."),
26963
27299
  inputSchema: ExtractUrlInputSchema,
26964
27300
  outputSchema: ExtractUrlOutputSchema,
26965
27301
  annotations: liveWebToolAnnotations("Single URL Extract")
26966
27302
  }, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
26967
27303
  server.registerTool("map_site_urls", {
26968
27304
  title: "Site URL Map",
26969
- description: withReportNote("Map/crawl a public website when the user asks for a sitemap, URL inventory, broken-link scan, redirect scan, or crawl planning. Returns internal URLs with HTTP status and truncation metadata. Supports up to maxUrls=10000; maps over 500 URLs write the complete inventory to a local CSV file and return a summary plus the file path instead of the full list inline. Use this before extract_site when choosing pages for an audit; use extract_url for one known page."),
27305
+ description: withReportNote("Map/crawl a public website for a sitemap, URL inventory, or broken-link scan. Returns internal URLs with HTTP status; maps over 500 URLs are written to a local CSV file instead of inlined."),
26970
27306
  inputSchema: MapSiteUrlsInputSchema,
26971
27307
  outputSchema: MapSiteUrlsOutputSchema,
26972
27308
  annotations: liveWebToolAnnotations("Site URL Map")
26973
27309
  }, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
26974
27310
  server.registerTool("extract_site", {
26975
27311
  title: "Multi-Page Site Content Crawl",
26976
- description: withReportNote("Crawl a public website and return the page CONTENT across multiple pages (map + scrape). Returns per-page titles and Markdown content. Supports up to maxPages=10000; bulk crawls over 25 pages switch to folder mode: each page is saved as its own Markdown file in a local folder and the response returns a summary plus the folder path instead of inlining all content. This tool returns content only \u2014 for a technical SEO audit (issues, internal link graph, indexability, heading breakdown, image sizes/formats) use audit_site. Use map_site_urls first when URL selection matters; use extract_url for one page."),
27312
+ description: withReportNote("Crawl a public website and return page CONTENT (Markdown) across multiple pages. Bulk crawls over 25 pages are saved as per-page Markdown files in a local folder instead of inlined. Content only \u2014 for a technical SEO audit use audit_site instead."),
26977
27313
  inputSchema: ExtractSiteInputSchema,
26978
27314
  outputSchema: ExtractSiteOutputSchema,
26979
27315
  annotations: liveWebToolAnnotations("Multi-Page Site Content Crawl")
26980
27316
  }, async (input) => formatExtractSite(await executor.extractSite(input), input));
26981
27317
  server.registerTool("audit_site", {
26982
27318
  title: "Technical SEO Audit",
26983
- description: withReportNote("Run a full technical SEO audit (Screaming-Frog-style) on a public website. Crawls up to maxPages and produces a complete on-page + crawl analysis: per-page title/meta lengths, heading breakdown, indexability and canonical status, schema, an internal link graph (inlinks/orphans/crawl-depth), a link analysis (top internal pages by inlinks, top external domains by link count, broken/orphan counts), an issues report, and an image audit (byte size, format, over-100KB and legacy-format flags). Writes everything to a local folder (report.md, issues.json, pages.jsonl, links.jsonl, link-metrics.jsonl, link-report.md, links-summary.json, external-domains.json, images.jsonl) plus per-page content, and returns a headline summary plus the folder path. Use this when the user wants an audit or analysis; use extract_site when they just want page content."),
27319
+ description: withReportNote("Run a full technical SEO audit (Screaming-Frog-style) on a public website: on-page issues, internal link graph, indexability, heading/image analysis. Writes a folder of analysis files plus per-page content, and returns a summary plus the folder path. Use extract_site instead for plain page content."),
26984
27320
  inputSchema: AuditSiteInputSchema,
26985
27321
  outputSchema: AuditSiteOutputSchema,
26986
27322
  annotations: liveWebToolAnnotations("Technical SEO Audit")
26987
27323
  }, async (input) => formatAuditSite(await executor.auditSite(input), input));
26988
27324
  server.registerTool("youtube_harvest", {
26989
27325
  title: "YouTube Video Harvest",
26990
- description: withReportNote('Harvest YouTube video metadata when the user wants to find videos by topic, inspect a channel library, compare video angles, or get videoIds for later transcription. Use mode "search" for keyword/topic requests and mode "channel" for @handles, channel IDs, or channel URLs. Returns titles, views, durations, URLs, and videoIds for follow-up transcription. Use youtube_transcribe after selecting one video.'),
27326
+ description: withReportNote('Harvest YouTube video metadata by topic search or channel library. Use mode "search" for keyword/topic requests, mode "channel" for @handles/channel IDs/URLs. Returns titles, views, durations, and videoIds.'),
26991
27327
  inputSchema: YoutubeHarvestInputSchema,
26992
27328
  outputSchema: YoutubeHarvestOutputSchema,
26993
27329
  annotations: liveWebToolAnnotations("YouTube Video Harvest")
26994
27330
  }, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
26995
27331
  server.registerTool("youtube_transcribe", {
26996
27332
  title: "YouTube Transcription",
26997
- description: withReportNote("Fetch and transcribe captions from a YouTube video. Use this when the user asks what was said in a YouTube video, wants claims/offers/lessons extracted from a video, or provides a YouTube URL for transcript work. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Pass videoId from youtube_harvest results, or pass url when the user pasted a YouTube URL. Use youtube_harvest first when you need to discover videos by topic/channel."),
27333
+ description: withReportNote("Fetch and transcribe captions from a YouTube video. Pass videoId from youtube_harvest, or a url the user pasted. Returns full transcript, timestamped chunks, and word count."),
26998
27334
  inputSchema: YoutubeTranscribeInputSchema,
26999
27335
  outputSchema: YoutubeTranscribeOutputSchema,
27000
27336
  annotations: liveWebToolAnnotations("YouTube Transcription")
27001
27337
  }, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
27002
27338
  server.registerTool("facebook_page_intel", {
27003
27339
  title: "Facebook Advertiser Ad Intel",
27004
- description: withReportNote("Harvest ads from a Facebook advertiser when the user wants current ad copy, creative angles, CTAs, video URLs, or competitive ad intelligence for one brand/page. Returns ad copy, headlines, CTAs, creative type, status, landing URLs, and direct ad video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name as query. Use facebook_ad_search first when the advertiser handle is unknown. For normal public Facebook reels/posts/watch/share URLs, use facebook_video_transcribe instead."),
27340
+ description: withReportNote("Harvest ads from a Facebook advertiser: copy, creative angles, CTAs, and direct video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name."),
27005
27341
  inputSchema: FacebookPageIntelInputSchema,
27006
27342
  outputSchema: FacebookPageIntelOutputSchema,
27007
27343
  annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
27008
27344
  }, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
27009
27345
  server.registerTool("facebook_ad_search", {
27010
27346
  title: "Facebook Ad Library Search",
27011
- description: withReportNote("Search Facebook Ad Library when the user wants to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs. Use this to discover competitor/page handles, then pass libraryId or pageId to facebook_page_intel for ad details."),
27347
+ description: withReportNote("Search Facebook Ad Library to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs."),
27012
27348
  inputSchema: FacebookAdSearchInputSchema,
27013
27349
  outputSchema: FacebookAdSearchOutputSchema,
27014
27350
  annotations: liveWebToolAnnotations("Facebook Ad Library Search")
27015
27351
  }, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
27016
27352
  server.registerTool("reddit_thread", {
27017
27353
  title: "Reddit Thread + Comments",
27018
- description: withReportNote("Capture a Reddit post and its comment tree from any reddit.com thread URL. Use this when the user wants Reddit comments, opinions, discussion, or audience voice from a specific thread (find threads first with search_serp, which surfaces reddit.com/comments links). Returns the post title, author, score, body, and a list of comments with author, score, nesting depth, and text. Handles Reddit's bot protection automatically (fetches via old.reddit through a residential proxy with retries); pass maxComments to cap the list."),
27354
+ description: withReportNote("Capture a Reddit post and its comment tree from a reddit.com thread URL \u2014 comments, opinions, audience voice. Handles Reddit's bot protection automatically; pass maxComments to cap the list."),
27019
27355
  inputSchema: RedditThreadInputSchema,
27020
27356
  outputSchema: RedditThreadOutputSchema,
27021
27357
  annotations: liveWebToolAnnotations("Reddit Thread + Comments")
27022
27358
  }, async (input) => formatRedditThread(await executor.redditThread(input), input));
27359
+ server.registerTool("video_frame_analysis", {
27360
+ title: "Video Breakdown (frame-by-frame + transcript)",
27361
+ description: "Produce a deep frame-by-frame + transcript breakdown of a video \u2014 pacing, hook, visual style, and how to replicate it. Pass a DIRECT video file URL (.mp4/.webm/.mov), not a page URL. Runs asynchronously and costs a flat $1 (refunded on failure): returns a runId immediately; poll video_frame_analysis_status until done.",
27362
+ inputSchema: VideoFrameAnalysisInputSchema,
27363
+ outputSchema: VideoFrameAnalysisOutputSchema,
27364
+ annotations: { title: "Video Breakdown", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
27365
+ }, async (input) => executor.videoFrameAnalysis(input));
27366
+ server.registerTool("video_frame_analysis_status", {
27367
+ title: "Video Breakdown Status",
27368
+ description: 'Check progress of a video breakdown started with video_frame_analysis, using its runId. Free to call. When status is "done" it returns the full report and vault path; stop polling on "done" or "failed".',
27369
+ inputSchema: VideoFrameAnalysisStatusInputSchema,
27370
+ outputSchema: VideoFrameAnalysisStatusOutputSchema,
27371
+ annotations: { title: "Video Breakdown Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
27372
+ }, async (input) => executor.videoFrameAnalysisStatus(input));
27023
27373
  server.registerTool("facebook_ad_transcribe", {
27024
27374
  title: "Facebook Ad Transcription",
27025
- description: "Transcribe audio from a Facebook ad video CDN URL. Use this when facebook_page_intel returned a direct videoUrl and the user asks what the ad says, what claims it makes, or wants ad-message extraction. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Use only with the direct videoUrl value from facebook_page_intel results; do not pass public Facebook post/reel/share URLs. Use facebook_video_transcribe for organic Facebook URLs, and facebook_page_intel first when you only have a brand/page/ad library handle.",
27375
+ description: "Transcribe audio from a Facebook ad video CDN URL returned by facebook_page_intel. Use only that direct videoUrl value \u2014 do not pass public Facebook post/reel/share URLs (use facebook_video_transcribe for those).",
27026
27376
  inputSchema: FacebookAdTranscribeInputSchema,
27027
27377
  outputSchema: FacebookAdTranscribeOutputSchema,
27028
27378
  annotations: liveWebToolAnnotations("Facebook Ad Transcription")
27029
27379
  }, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
27030
27380
  server.registerTool("google_ads_search", {
27031
27381
  title: "Google Ads Transparency Search",
27032
- description: withReportNote("Search the Google Ads Transparency Center to find advertisers running Google ads (Search, Display, YouTube) by domain or brand/advertiser name. Pass a domain like getviktor.com to see every advertiser account pointing ads at that site, or an advertiser name. Returns advertisers with their advertiser ID and an approximate ad count. Use this to discover an advertiserId, then pass it (or the domain) to google_ads_page_intel for the actual ad creatives."),
27382
+ description: withReportNote("Search the Google Ads Transparency Center to find advertisers by domain or brand name. Returns advertisers with advertiser ID and approximate ad count, to pass to google_ads_page_intel."),
27033
27383
  inputSchema: GoogleAdsSearchInputSchema,
27034
27384
  outputSchema: GoogleAdsSearchOutputSchema,
27035
27385
  annotations: liveWebToolAnnotations("Google Ads Transparency Search")
27036
27386
  }, async (input) => formatGoogleAdsSearch(await executor.googleAdsSearch(input), input));
27037
27387
  server.registerTool("google_ads_page_intel", {
27038
27388
  title: "Google Ads Advertiser Intel",
27039
- description: withReportNote("Harvest an advertiser's ads from the Google Ads Transparency Center when the user wants a competitor's Google/YouTube ad creatives, copy angles, image URLs, or video ads. Accepts an advertiserId (from google_ads_search) or a domain directly. Returns each creative's format (text/image/video), image URLs, landing info, a detail URL, and \u2014 for video ads \u2014 the YouTube video ID and/or a direct video URL ready for transcription. Use google_ads_search first when you only have a brand name. Transcribe video ads with google_ads_transcribe (video URL) or youtube_transcribe (YouTube ID)."),
27389
+ description: withReportNote("Harvest an advertiser's ad creatives from the Google Ads Transparency Center: format, image URLs, and \u2014 for video ads \u2014 a YouTube video ID or direct video URL. Accepts advertiserId (from google_ads_search) or a domain."),
27040
27390
  inputSchema: GoogleAdsPageIntelInputSchema,
27041
27391
  outputSchema: GoogleAdsPageIntelOutputSchema,
27042
27392
  annotations: liveWebToolAnnotations("Google Ads Advertiser Intel")
27043
27393
  }, async (input) => formatGoogleAdsPageIntel(await executor.googleAdsPageIntel(input), input));
27044
27394
  server.registerTool("google_ads_transcribe", {
27045
27395
  title: "Google Ad Video Transcription",
27046
- description: "Transcribe audio from a Google video ad. Use this when google_ads_page_intel returned a direct videoUrl (a googlevideo.com playback URL) for a video creative and the user asks what the ad says or wants its claims/messaging. Returns full transcript, timestamped chunks, and word count. For YouTube-hosted ads, pass the returned youtubeVideoId to youtube_transcribe instead; run google_ads_page_intel first when you only have an advertiser/domain.",
27396
+ description: "Transcribe audio from a Google video ad's direct videoUrl (a googlevideo.com playback URL) returned by google_ads_page_intel. For YouTube-hosted ads, use youtube_transcribe with the returned youtubeVideoId instead.",
27047
27397
  inputSchema: GoogleAdsTranscribeInputSchema,
27048
27398
  outputSchema: GoogleAdsTranscribeOutputSchema,
27049
27399
  annotations: liveWebToolAnnotations("Google Ad Video Transcription")
27050
27400
  }, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
27051
27401
  server.registerTool("facebook_video_transcribe", {
27052
27402
  title: "Facebook Organic Video Transcription",
27053
- description: withReportNote("Transcribe audio from an organic Facebook reel, video, watch, post, or share URL, including fb.watch links. Use this when the user pastes a normal Facebook video page URL and wants the transcript or downloadable MP4. Renders the Facebook page in a browser, selects the best matching public Facebook CDN MP4 URL from page state, then returns sourceUrl, resolved pageUrl, videoId, ownerName, selectedQuality, bitrate, videoDurationSec, extracted MP4 URL, full transcript, and timestamped chunks."),
27403
+ description: withReportNote("Transcribe audio from an organic Facebook reel/video/post/share URL (including fb.watch). Renders the page, selects the best public CDN MP4, and returns the transcript plus resolved video metadata."),
27054
27404
  inputSchema: FacebookVideoTranscribeInputSchema,
27055
27405
  outputSchema: FacebookVideoTranscribeOutputSchema,
27056
27406
  annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
27057
27407
  }, async (input) => formatFacebookVideoTranscribe(await executor.facebookVideoTranscribe(input), input));
27058
27408
  server.registerTool("instagram_profile_content", {
27059
27409
  title: "Instagram Profile Content Discovery",
27060
- description: withReportNote("Discover Instagram profile grid content links for a handle or profile URL. Use this when the user wants a person or brand account content inventory before selecting posts/reels to download. Returns profile stats, collected post/reel/tv URLs, shortcodes, type counts, browser details, pagination attempts, stop reason, and limitations."),
27410
+ description: withReportNote("Discover Instagram profile grid content links (posts/reels/tv) for a handle or profile URL, for later selection with instagram_media_download. Returns profile stats and collected URLs."),
27061
27411
  inputSchema: InstagramProfileContentInputSchema,
27062
27412
  outputSchema: InstagramProfileContentOutputSchema,
27063
27413
  annotations: liveWebToolAnnotations("Instagram Profile Content Discovery")
27064
27414
  }, async (input) => formatInstagramProfileContent(await executor.instagramProfileContent(input), input));
27065
27415
  server.registerTool("instagram_media_download", {
27066
27416
  title: "Instagram Post/Reel Media Download",
27067
- description: withReportNote("Extract and download media from one Instagram post, reel, or tv URL. Use after instagram_profile_content or when the user gives a specific Instagram URL and wants the image, caption/text, reel audio/video tracks, optional muxed MP4, or optional transcript. Reels commonly expose separate video-only and audio-only MP4 tracks; this tool selects the best video and audio tracks and attempts muxing when ffmpeg is available."),
27417
+ description: withReportNote("Extract and download media from one Instagram post, reel, or tv URL \u2014 image, caption, video/audio tracks, optional muxed MP4, or transcript. Selects the best video/audio track pair and muxes when ffmpeg is available."),
27068
27418
  inputSchema: InstagramMediaDownloadInputSchema,
27069
27419
  outputSchema: InstagramMediaDownloadOutputSchema,
27070
27420
  annotations: liveWebToolAnnotations("Instagram Post/Reel Media Download")
27071
27421
  }, async (input) => formatInstagramMediaDownload(await executor.instagramMediaDownload(input), input));
27072
27422
  server.registerTool("maps_place_intel", {
27073
27423
  title: "Google Maps Business Profile Details",
27074
- description: withReportNote('Extract Google Maps business intelligence for one known/named business: rating, review count, category, address, phone, website, hours, booking URL, review histogram, review topics, about attributes, entity IDs, and optional review cards. Do not use this for category searches, local market prospect lists, or requests for multiple GMB/GBP profiles; use maps_search first for those. Split business name from location (e.g. "Elite Roofing Denver CO" => businessName "Elite Roofing", location "Denver, CO"). Pass includeReviews true when the user asks for reviews/customer pain.'),
27424
+ description: withReportNote("Extract Google Maps business intelligence for one known/named business: rating, reviews, category, address, phone, hours, entity IDs. Not for category searches or multi-business prospect lists \u2014 use maps_search for those. Split business name from location."),
27075
27425
  inputSchema: MapsPlaceIntelInputSchema,
27076
27426
  outputSchema: MapsPlaceIntelOutputSchema,
27077
27427
  annotations: liveWebToolAnnotations("Google Maps Business Profile Details")
27078
27428
  }, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
27079
27429
  server.registerTool("maps_search", {
27080
27430
  title: "Google Maps Business Search",
27081
- description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack." Routing is automatic and location-aware \u2014 leave proxyMode unset; pass proxyZip only to force a specific ZIP. Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, profile metadata, and sanitized attempt telemetry. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
27431
+ description: withReportNote("Search Google Maps for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack. Leave proxyMode unset. Returns up to 50 candidates (default 10) with names, place URLs, CIDs, ratings."),
27082
27432
  inputSchema: MapsSearchInputSchema,
27083
27433
  outputSchema: MapsSearchOutputSchema,
27084
27434
  annotations: liveWebToolAnnotations("Google Maps Business Search")
27085
27435
  }, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
27086
27436
  server.registerTool("directory_workflow", {
27087
27437
  title: "Directory Workflow: Markets + Maps",
27088
- description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Routing is automatic per city. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
27438
+ description: withReportNote('Build directory/prospecting datasets: selects US city markets from Census population data, optionally joins configured ZIP groups, then runs Google Maps business searches per city in parallel. Use for "all cities over 100k population in a state" or market+Maps workflows. Saves a CSV of results per city. For ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the server, or pass usZipsCsvPath in local/test mode.'),
27089
27439
  inputSchema: DirectoryWorkflowInputSchema,
27090
27440
  outputSchema: DirectoryWorkflowOutputSchema,
27091
27441
  annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
27092
27442
  }, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input));
27093
27443
  server.registerTool("workflow_list", {
27094
27444
  title: "Workflow Catalog",
27095
- description: "List MCP Scraper higher-level workflows and AI-facing recipes. Use this when the user asks what MCP Scraper can do beyond single tools, or asks for market analysis, ICP research, forum/review acquisition, full brand design briefings, CRO audits, competitive positioning, content gap briefs, or AI search visibility audits. Returns runnable workflow ids plus recipe guidance for the best tool chain.",
27445
+ description: "List MCP Scraper higher-level workflows and recipes \u2014 market analysis, ICP research, CRO audits, competitive positioning, content gap briefs, AI search visibility, and more. Returns runnable workflow ids plus tool-chain guidance.",
27096
27446
  inputSchema: WorkflowListInputSchema,
27097
27447
  outputSchema: WorkflowListOutputSchema,
27098
27448
  annotations: localPlanningToolAnnotations("Workflow Catalog")
27099
27449
  }, async (input) => formatWorkflowList(await executor.workflowList(input), input));
27100
27450
  server.registerTool("workflow_suggest", {
27101
27451
  title: "Workflow Intent Router",
27102
- description: 'Route a high-level business or research goal to the right MCP Scraper workflow/tool chain. Use before running work when the user says things like "do market analysis", "research my ICP", "acquire forum and review language", "build a brand design brief", "run a CRO audit", "compare competitors", "make a content gap brief", or "audit AI search visibility". This tool does not spend credits; it tells the AI exactly which workflow/tool chain to run next.',
27452
+ description: "Route a high-level business/research goal (market analysis, ICP research, CRO audit, competitor comparison, content gap brief, AI search visibility, etc) to the right MCP Scraper workflow/tool chain. Free; tells you what to run next.",
27103
27453
  inputSchema: WorkflowSuggestInputSchema,
27104
27454
  outputSchema: WorkflowSuggestOutputSchema,
27105
27455
  annotations: localPlanningToolAnnotations("Workflow Intent Router")
27106
27456
  }, async (input) => formatWorkflowSuggest(input));
27107
27457
  server.registerTool("workflow_run", {
27108
27458
  title: "Run Workflow",
27109
- description: withReportNote("Start a higher-level MCP Scraper workflow. Use after workflow_suggest or workflow_list. Runnable workflow ids: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. This is the main MCP tool for market analysis, ICP evidence packets, local competitive audits, Maps/SERP comparisons, content gap briefs, and AI Overview language guidance. Stepwise workflows (e.g. agent-packet) run ONE leg per call and return runId, the step output, and nextStep \u2014 when nextStep is present, call workflow_step with the runId to run the next leg, and keep calling it until done is true. This keeps each call short instead of one long blocking run, so report each step result to the user as it arrives."),
27459
+ description: withReportNote("Start a higher-level MCP Scraper workflow (directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language). Use after workflow_suggest or workflow_list. Stepwise workflows return runId + nextStep \u2014 call workflow_step with the runId until done is true."),
27110
27460
  inputSchema: WorkflowRunInputSchema,
27111
27461
  outputSchema: WorkflowRunOutputSchema,
27112
27462
  annotations: liveWebToolAnnotations("Run Workflow")
27113
27463
  }, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
27114
27464
  server.registerTool("workflow_step", {
27115
27465
  title: "Advance Workflow Step",
27116
- description: withReportNote("Run the next leg of a stepwise MCP Scraper workflow started with workflow_run. Pass the runId. Each call executes exactly one logical step (typically one live harvest), persists that step's artifacts, and returns the step output plus nextStep. Keep calling workflow_step with the same runId until done is true, reporting each step result to the user as it lands. Use this instead of waiting on one long workflow call \u2014 it avoids client timeouts on long multi-step jobs."),
27466
+ description: withReportNote("Run the next leg of a stepwise workflow started with workflow_run. Pass the runId; each call executes one step and returns its output plus nextStep. Keep calling with the same runId until done is true."),
27117
27467
  inputSchema: WorkflowStepInputSchema,
27118
27468
  outputSchema: WorkflowStepOutputSchema,
27119
27469
  annotations: liveWebToolAnnotations("Advance Workflow Step")
27120
27470
  }, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
27121
27471
  server.registerTool("workflow_status", {
27122
27472
  title: "Workflow Status",
27123
- description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use when a workflow may still be running, when the model needs to re-open a run, inspect artifact ids, recover from a long workflow, or decide whether to call workflow_step or workflow_artifact_read next. Use only a runId returned by workflow_run/workflow_step/workflow_status; do not construct one yourself.",
27473
+ description: "Fetch a hosted workflow run by id and list its current status and artifacts, to re-open a run or recover artifact ids. Use only a runId returned by workflow_run/workflow_step/workflow_status.",
27124
27474
  inputSchema: WorkflowStatusInputSchema,
27125
27475
  outputSchema: WorkflowStatusOutputSchema,
27126
27476
  annotations: liveWebToolAnnotations("Workflow Status")
27127
27477
  }, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
27128
27478
  server.registerTool("workflow_artifact_read", {
27129
27479
  title: "Read Workflow Artifact",
27130
- description: "Read a workflow artifact back into MCP context by run id and artifact id. Use this before writing final deliverables so the answer is grounded in generated evidence.json, CSVs, Markdown briefs, reports, and task files instead of memory. Use workflow_status first when artifact ids are unknown. Use only artifactId values returned by workflow_run/workflow_step/workflow_status; do not construct one yourself. Use maxBytes to limit large CSV/JSON artifacts.",
27480
+ description: "Read a workflow artifact back into context by run id and artifact id, so final deliverables are grounded in generated evidence rather than memory. Use workflow_status first when artifact ids are unknown. Use maxBytes to limit large artifacts.",
27131
27481
  inputSchema: WorkflowArtifactReadInputSchema,
27132
27482
  outputSchema: WorkflowArtifactReadOutputSchema,
27133
27483
  annotations: liveWebToolAnnotations("Read Workflow Artifact")
27134
27484
  }, async (input) => formatWorkflowArtifactRead(await executor.workflowArtifactRead(input), input));
27135
27485
  server.registerTool("rank_tracker_workflow", {
27136
27486
  title: "Rank Tracker Blueprint Builder",
27137
- description: "Generate a build-ready database, cron/heartbeat, ingestion, metrics, and AI implementation prompt for a rank tracker powered by MCP Scraper. Supports Maps rankings through directory_workflow/maps_search, organic rankings through search_serp, AI Overview citation tracking, and People Also Ask source presence tracking. This tool is local planning only; it does not call the web or spend credits.",
27487
+ description: "Generate a build-ready database schema, cron plan, and implementation prompt for a rank tracker powered by MCP Scraper (Maps, organic, AI Overview, or PAA tracking). Local planning only \u2014 does not call the web or spend credits.",
27138
27488
  inputSchema: RankTrackerBlueprintInputSchema,
27139
27489
  outputSchema: RankTrackerBlueprintOutputSchema,
27140
27490
  annotations: localPlanningToolAnnotations("Rank Tracker Blueprint Builder")
27141
27491
  }, async (input) => buildRankTrackerBlueprint(input));
27142
27492
  server.registerTool("credits_info", {
27143
27493
  title: "MCP Scraper Credits & Costs",
27144
- description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades: current credit balance, what a specific tool/action costs, the full cost table, current concurrency limit, the extra-slot price, billing URL, and the terminal checkout command. Does not expose payment methods or credit card information.",
27494
+ description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades \u2014 balance, tool costs, concurrency limits, billing URL. Does not expose payment methods or card information.",
27145
27495
  inputSchema: CreditsInfoInputSchema,
27146
27496
  outputSchema: CreditsInfoOutputSchema,
27147
27497
  annotations: {
@@ -27382,6 +27732,12 @@ var init_http_mcp_tool_executor = __esm({
27382
27732
  redditThread(input) {
27383
27733
  return this.call("/reddit/thread", input, this.httpTimeoutOverrideMs ?? 24e4);
27384
27734
  }
27735
+ videoFrameAnalysis(input) {
27736
+ return this.call("/video/analyze", input);
27737
+ }
27738
+ videoFrameAnalysisStatus(input) {
27739
+ return this.call("/video/status", input);
27740
+ }
27385
27741
  facebookAdTranscribe(input) {
27386
27742
  return this.call("/facebook/transcribe", input);
27387
27743
  }
@@ -27588,17 +27944,17 @@ var init_export = __esm({
27588
27944
  });
27589
27945
 
27590
27946
  // src/mcp/browser-agent-tool-schemas.ts
27591
- var import_zod31, NullableString2, BrowserRawObject, BrowserElementOutput, BrowserBaseOutput, BrowserReplayBaseOutput, BrowserOpenInputSchema, BrowserProfileConnectInputSchema, BrowserProfileListInputSchema, BrowserSessionInputSchema, BrowserLocateTargetSchema, BrowserLocateInputSchema, BrowserGotoInputSchema, BrowserClickInputSchema, BrowserTypeInputSchema, BrowserScrollInputSchema, BrowserPressInputSchema, BrowserReplayStopInputSchema, BrowserReplayDownloadInputSchema, BrowserReplayAnnotationSchema, BrowserReplayMarkInputSchema, BrowserReplayAnnotateInputSchema, BrowserListInputSchema, BrowserCaptureFanoutInputSchema, FanoutSourceOutput, BrowserCaptureFanoutOutputSchema, BrowserOpenOutputSchema, BrowserProfileLogin, BrowserProfileConnectOutputSchema, BrowserProfileListOutputSchema, BrowserScreenshotOutputSchema, BrowserReadOutputSchema, BrowserLocateOutputSchema, BrowserActionOutputSchema, BrowserReplayStartOutputSchema, BrowserReplayStopOutputSchema, BrowserListReplaysOutputSchema, BrowserReplayDownloadOutputSchema, BrowserReplayMarkOutputSchema, BrowserReplayAnnotateOutputSchema, BrowserCloseOutputSchema, BrowserListSessionsOutputSchema;
27947
+ var import_zod32, NullableString2, BrowserRawObject, BrowserElementOutput, BrowserBaseOutput, BrowserReplayBaseOutput, BrowserOpenInputSchema, BrowserProfileConnectInputSchema, BrowserProfileListInputSchema, BrowserSessionInputSchema, BrowserLocateTargetSchema, BrowserLocateInputSchema, BrowserGotoInputSchema, BrowserClickInputSchema, BrowserTypeInputSchema, BrowserScrollInputSchema, BrowserPressInputSchema, BrowserReplayStopInputSchema, BrowserReplayDownloadInputSchema, BrowserReplayAnnotationSchema, BrowserReplayMarkInputSchema, BrowserReplayAnnotateInputSchema, BrowserListInputSchema, BrowserCaptureFanoutInputSchema, FanoutSourceOutput, BrowserCaptureFanoutOutputSchema, BrowserOpenOutputSchema, BrowserProfileLogin, BrowserProfileConnectOutputSchema, BrowserProfileListOutputSchema, BrowserScreenshotOutputSchema, BrowserReadOutputSchema, BrowserLocateOutputSchema, BrowserActionOutputSchema, BrowserReplayStartOutputSchema, BrowserReplayStopOutputSchema, BrowserListReplaysOutputSchema, BrowserReplayDownloadOutputSchema, BrowserReplayMarkOutputSchema, BrowserReplayAnnotateOutputSchema, BrowserCloseOutputSchema, BrowserListSessionsOutputSchema;
27592
27948
  var init_browser_agent_tool_schemas = __esm({
27593
27949
  "src/mcp/browser-agent-tool-schemas.ts"() {
27594
27950
  "use strict";
27595
- import_zod31 = require("zod");
27596
- NullableString2 = import_zod31.z.string().nullable();
27597
- BrowserRawObject = import_zod31.z.record(import_zod31.z.unknown());
27598
- BrowserElementOutput = import_zod31.z.record(import_zod31.z.unknown());
27951
+ import_zod32 = require("zod");
27952
+ NullableString2 = import_zod32.z.string().nullable();
27953
+ BrowserRawObject = import_zod32.z.record(import_zod32.z.unknown());
27954
+ BrowserElementOutput = import_zod32.z.record(import_zod32.z.unknown());
27599
27955
  BrowserBaseOutput = {
27600
- ok: import_zod31.z.boolean().describe("Whether the browser-agent action succeeded."),
27601
- tool: import_zod31.z.string().describe("Browser Agent MCP tool that produced this response."),
27956
+ ok: import_zod32.z.boolean().describe("Whether the browser-agent action succeeded."),
27957
+ tool: import_zod32.z.string().describe("Browser Agent MCP tool that produced this response."),
27602
27958
  session_id: NullableString2.describe("Browser session id when the response is scoped to a session.")
27603
27959
  };
27604
27960
  BrowserReplayBaseOutput = {
@@ -27606,333 +27962,333 @@ var init_browser_agent_tool_schemas = __esm({
27606
27962
  replay_id: NullableString2.describe("Replay id when the response is scoped to a replay.")
27607
27963
  };
27608
27964
  BrowserOpenInputSchema = {
27609
- label: import_zod31.z.string().optional().describe("Optional human label for this session, shown in the watch console."),
27610
- url: import_zod31.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
27611
- profile: import_zod31.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
27612
- save_profile_changes: import_zod31.z.boolean().optional().describe("Persist cookies and browser storage back to the named profile when the session is closed. Use this for profile setup or intentional auth refreshes; avoid parallel sessions writing to the same profile."),
27613
- timeout_seconds: import_zod31.z.number().int().min(60).max(259200).optional().describe("How long the direct no-proxy browser session may live before auto-termination. Defaults to 600. The browser idles into a zero-cost standby between actions, so a longer timeout is cheap.")
27965
+ label: import_zod32.z.string().optional().describe("Optional human label for this session, shown in the watch console."),
27966
+ url: import_zod32.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
27967
+ profile: import_zod32.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
27968
+ save_profile_changes: import_zod32.z.boolean().optional().describe("Persist cookies/storage back to the named profile on close. Avoid parallel sessions writing to the same profile."),
27969
+ timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
27614
27970
  };
27615
27971
  BrowserProfileConnectInputSchema = {
27616
- email: import_zod31.z.string().optional().describe("Account email for the login being saved. Used to derive a stable, reusable profile name AND recorded as a note of who is connected. Does NOT import existing cookies \u2014 the user signs in fresh via the returned watch_url."),
27617
- profile: import_zod31.z.string().optional().describe("Profile to add this login to. Omit to derive one from email. A single profile holds MANY site logins \u2014 pass the same profile name again with a different domain to stack another account (e.g. add claude.ai to a profile that already has chatgpt.com)."),
27618
- domain: import_zod31.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com. Each domain is a separate login inside the profile."),
27619
- login_url: import_zod31.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain>/ (https://chatgpt.com/ when domain is omitted)."),
27620
- url: import_zod31.z.string().url().optional().describe("Deprecated alias for login_url. Use login_url."),
27621
- note: import_zod31.z.string().optional().describe('Free-text note describing this login, e.g. "ChatGPT Plus, work account". Surfaced by browser_profile_list so you can see what the profile holds.'),
27622
- label: import_zod31.z.string().optional().describe("Optional human label for this sign-in setup session."),
27623
- timeout_seconds: import_zod31.z.number().int().min(60).max(259200).optional().describe("How long the sign-in setup session may live before auto-termination. Defaults to 600.")
27972
+ email: import_zod32.z.string().optional().describe("Account email for the login. Derives a stable profile name and is recorded as a note. Does NOT import existing cookies \u2014 the user signs in fresh."),
27973
+ profile: import_zod32.z.string().optional().describe("Profile to add this login to. Omit to derive from email. A single profile holds MANY logins \u2014 pass the same name with a different domain to stack accounts."),
27974
+ domain: import_zod32.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com."),
27975
+ login_url: import_zod32.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain>/."),
27976
+ url: import_zod32.z.string().url().optional().describe("Deprecated alias for login_url."),
27977
+ note: import_zod32.z.string().optional().describe("Free-text note describing this login. Surfaced by browser_profile_list."),
27978
+ label: import_zod32.z.string().optional().describe("Optional human label for this sign-in setup session."),
27979
+ timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("Sign-in session lifetime before auto-termination. Defaults to 600.")
27624
27980
  };
27625
27981
  BrowserProfileListInputSchema = {
27626
- profile: import_zod31.z.string().optional().describe("Profile whose saved logins to list. Omit to derive from email."),
27627
- email: import_zod31.z.string().optional().describe("Account email used to derive the profile name when profile is not given."),
27628
- domain: import_zod31.z.string().optional().describe("Restrict to one site login, e.g. chatgpt.com. Use this to poll a single login until its status reads AUTHENTICATED."),
27629
- connection_id: import_zod31.z.string().optional().describe("A specific login connection id returned by browser_profile_connect, to poll just that one.")
27982
+ profile: import_zod32.z.string().optional().describe("Profile whose saved logins to list. Omit to derive from email."),
27983
+ email: import_zod32.z.string().optional().describe("Account email used to derive the profile name when profile is not given."),
27984
+ domain: import_zod32.z.string().optional().describe("Restrict to one site login, e.g. chatgpt.com. Use this to poll a single login until its status reads AUTHENTICATED."),
27985
+ connection_id: import_zod32.z.string().optional().describe("A specific login connection id returned by browser_profile_connect, to poll just that one.")
27630
27986
  };
27631
27987
  BrowserSessionInputSchema = {
27632
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself.")
27988
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.")
27633
27989
  };
27634
- BrowserLocateTargetSchema = import_zod31.z.object({
27635
- name: import_zod31.z.string().optional().describe("Optional label for this target, echoed in the result."),
27636
- selector: import_zod31.z.string().optional().describe('CSS selector for the exact DOM element to locate, for example h1, input[name="q"], or [data-testid="result"].'),
27637
- text: import_zod31.z.string().optional().describe("Visible text to locate when a selector is not known. The tool returns the text range bounds when possible."),
27638
- match: import_zod31.z.enum(["contains", "exact"]).default("contains").describe("How to match text targets. Defaults to contains."),
27639
- index: import_zod31.z.number().int().min(0).default(0).describe("Zero-based match index when multiple elements match.")
27990
+ BrowserLocateTargetSchema = import_zod32.z.object({
27991
+ name: import_zod32.z.string().optional().describe("Optional label for this target, echoed in the result."),
27992
+ selector: import_zod32.z.string().optional().describe('CSS selector for the exact DOM element to locate, for example h1, input[name="q"], or [data-testid="result"].'),
27993
+ text: import_zod32.z.string().optional().describe("Visible text to locate when a selector is not known. The tool returns the text range bounds when possible."),
27994
+ match: import_zod32.z.enum(["contains", "exact"]).default("contains").describe("How to match text targets. Defaults to contains."),
27995
+ index: import_zod32.z.number().int().min(0).default(0).describe("Zero-based match index when multiple elements match.")
27640
27996
  }).refine((value) => Boolean(value.selector || value.text), {
27641
27997
  message: "target requires selector or text"
27642
27998
  });
27643
27999
  BrowserLocateInputSchema = {
27644
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27645
- targets: import_zod31.z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
28000
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28001
+ targets: import_zod32.z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
27646
28002
  };
27647
28003
  BrowserGotoInputSchema = {
27648
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27649
- url: import_zod31.z.string().url().describe("URL to navigate the browser to.")
28004
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28005
+ url: import_zod32.z.string().url().describe("URL to navigate the browser to.")
27650
28006
  };
27651
28007
  BrowserClickInputSchema = {
27652
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27653
- x: import_zod31.z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
27654
- y: import_zod31.z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
27655
- button: import_zod31.z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
27656
- num_clicks: import_zod31.z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
28008
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28009
+ x: import_zod32.z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
28010
+ y: import_zod32.z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
28011
+ button: import_zod32.z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
28012
+ num_clicks: import_zod32.z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
27657
28013
  };
27658
28014
  BrowserTypeInputSchema = {
27659
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27660
- text: import_zod31.z.string().describe("Text to type at the current focus. Click a field first to focus it."),
27661
- delay: import_zod31.z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
28015
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28016
+ text: import_zod32.z.string().describe("Text to type at the current focus. Click a field first to focus it."),
28017
+ delay: import_zod32.z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
27662
28018
  };
27663
28019
  BrowserScrollInputSchema = {
27664
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27665
- delta_y: import_zod31.z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
27666
- delta_x: import_zod31.z.number().default(0).describe("Horizontal scroll in wheel units."),
27667
- x: import_zod31.z.number().optional().describe("X position to scroll at. Defaults to screen center."),
27668
- y: import_zod31.z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
28020
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28021
+ delta_y: import_zod32.z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
28022
+ delta_x: import_zod32.z.number().default(0).describe("Horizontal scroll in wheel units."),
28023
+ x: import_zod32.z.number().optional().describe("X position to scroll at. Defaults to screen center."),
28024
+ y: import_zod32.z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
27669
28025
  };
27670
28026
  BrowserPressInputSchema = {
27671
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27672
- keys: import_zod31.z.array(import_zod31.z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
28027
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28028
+ keys: import_zod32.z.array(import_zod32.z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
27673
28029
  };
27674
28030
  BrowserReplayStopInputSchema = {
27675
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27676
- replay_id: import_zod31.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself.")
28031
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28032
+ replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.")
27677
28033
  };
27678
28034
  BrowserReplayDownloadInputSchema = {
27679
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27680
- replay_id: import_zod31.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
27681
- filename: import_zod31.z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
28035
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28036
+ replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
28037
+ filename: import_zod32.z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
27682
28038
  };
27683
- BrowserReplayAnnotationSchema = import_zod31.z.object({
27684
- type: import_zod31.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style to draw over the replay."),
27685
- start_seconds: import_zod31.z.number().min(0).default(0).describe("When the annotation should appear in the replay."),
27686
- end_seconds: import_zod31.z.number().min(0).optional().describe("When the annotation should disappear. Defaults to two seconds after start_seconds."),
27687
- left: import_zod31.z.number().optional().describe("Target left edge in browser screenshot pixels. Use element.left from browser_screenshot/browser_read."),
27688
- top: import_zod31.z.number().optional().describe("Target top edge in browser screenshot pixels. Use element.top from browser_screenshot/browser_read."),
27689
- width: import_zod31.z.number().positive().optional().describe("Target width in browser screenshot pixels. Use element.width from browser_screenshot/browser_read."),
27690
- height: import_zod31.z.number().positive().optional().describe("Target height in browser screenshot pixels. Use element.height from browser_screenshot/browser_read."),
27691
- x: import_zod31.z.number().optional().describe("Point target x coordinate in browser screenshot pixels when no box is available."),
27692
- y: import_zod31.z.number().optional().describe("Point target y coordinate in browser screenshot pixels when no box is available."),
27693
- from_x: import_zod31.z.number().optional().describe("Arrow start x coordinate in browser screenshot pixels. Defaults near the target."),
27694
- from_y: import_zod31.z.number().optional().describe("Arrow start y coordinate in browser screenshot pixels. Defaults near the target."),
27695
- to_x: import_zod31.z.number().optional().describe("Arrow target x coordinate in browser screenshot pixels. Defaults to the target box center."),
27696
- to_y: import_zod31.z.number().optional().describe("Arrow target y coordinate in browser screenshot pixels. Defaults to the target box center."),
27697
- label: import_zod31.z.string().max(120).optional().describe("Optional text callout to render near the annotation."),
27698
- color: import_zod31.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, for example #ff3b30."),
27699
- thickness: import_zod31.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
28039
+ BrowserReplayAnnotationSchema = import_zod32.z.object({
28040
+ type: import_zod32.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style."),
28041
+ start_seconds: import_zod32.z.number().min(0).default(0).describe("When the annotation should appear."),
28042
+ end_seconds: import_zod32.z.number().min(0).optional().describe("When it disappears. Defaults to 2s after start_seconds."),
28043
+ left: import_zod32.z.number().optional().describe("Target left edge in screenshot pixels (element.left)."),
28044
+ top: import_zod32.z.number().optional().describe("Target top edge in screenshot pixels (element.top)."),
28045
+ width: import_zod32.z.number().positive().optional().describe("Target width in screenshot pixels (element.width)."),
28046
+ height: import_zod32.z.number().positive().optional().describe("Target height in screenshot pixels (element.height)."),
28047
+ x: import_zod32.z.number().optional().describe("Point target x coordinate when no box is available."),
28048
+ y: import_zod32.z.number().optional().describe("Point target y coordinate when no box is available."),
28049
+ from_x: import_zod32.z.number().optional().describe("Arrow start x coordinate. Defaults near the target."),
28050
+ from_y: import_zod32.z.number().optional().describe("Arrow start y coordinate. Defaults near the target."),
28051
+ to_x: import_zod32.z.number().optional().describe("Arrow end x coordinate. Defaults to the target box center."),
28052
+ to_y: import_zod32.z.number().optional().describe("Arrow end y coordinate. Defaults to the target box center."),
28053
+ label: import_zod32.z.string().max(120).optional().describe("Optional text callout."),
28054
+ color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
28055
+ thickness: import_zod32.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
27700
28056
  });
27701
28057
  BrowserReplayMarkInputSchema = {
27702
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself. A replay must already be recording."),
28058
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions. A replay must already be recording."),
27703
28059
  target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
27704
- type: import_zod31.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate. Labels are included on the returned annotation when label is provided."),
27705
- label: import_zod31.z.string().max(120).optional().describe("Optional callout text to render near the target."),
27706
- color: import_zod31.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, for example #ff3b30."),
27707
- thickness: import_zod31.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5."),
27708
- padding: import_zod31.z.number().min(0).max(80).default(8).describe("Pixels to expand the DOM bounds so the highlight does not touch the text edge."),
27709
- start_offset_seconds: import_zod31.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time. Negative values make the callout appear just before the mark action is captured."),
27710
- duration_seconds: import_zod31.z.number().min(0.5).max(30).default(4).describe("How long the generated annotation should remain visible.")
28060
+ type: import_zod32.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate."),
28061
+ label: import_zod32.z.string().max(120).optional().describe("Optional callout text to render near the target."),
28062
+ color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
28063
+ thickness: import_zod32.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5."),
28064
+ padding: import_zod32.z.number().min(0).max(80).default(8).describe("Pixels to expand the DOM bounds so the highlight does not touch the text edge."),
28065
+ start_offset_seconds: import_zod32.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time; negative appears just before the mark action."),
28066
+ duration_seconds: import_zod32.z.number().min(0.5).max(30).default(4).describe("How long the annotation should remain visible.")
27711
28067
  };
27712
28068
  BrowserReplayAnnotateInputSchema = {
27713
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself."),
27714
- replay_id: import_zod31.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
27715
- annotations: import_zod31.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations to render on the replay. Prefer annotations returned by browser_replay_mark; otherwise use exact DOM bounds from browser_locate/browser_screenshot/browser_read."),
27716
- filename: import_zod31.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped annotated replay filename."),
27717
- source_width: import_zod31.z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
27718
- source_height: import_zod31.z.number().positive().optional().describe("Height of the coordinate space used for annotations. When this is a page viewport height smaller than the replay video height, the annotator infers the browser chrome top offset."),
27719
- source_left_offset: import_zod31.z.number().min(0).optional().describe("Optional explicit X offset from annotation coordinates to replay video coordinates. Usually omitted."),
27720
- source_top_offset: import_zod31.z.number().min(0).optional().describe("Optional explicit Y offset from annotation coordinates to replay video coordinates. Usually omitted because browser chrome offset is inferred when source_height is a page viewport height.")
28069
+ session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
28070
+ replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
28071
+ annotations: import_zod32.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations. Prefer ones from browser_replay_mark; otherwise use exact DOM bounds from browser_locate."),
28072
+ filename: import_zod32.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped filename."),
28073
+ source_width: import_zod32.z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
28074
+ source_height: import_zod32.z.number().positive().optional().describe("Height of the annotation coordinate space; if smaller than the replay video height, the browser chrome offset is inferred."),
28075
+ source_left_offset: import_zod32.z.number().min(0).optional().describe("Explicit X offset from annotation to replay video coordinates. Usually omitted."),
28076
+ source_top_offset: import_zod32.z.number().min(0).optional().describe("Explicit Y offset from annotation to replay video coordinates. Usually omitted.")
27721
28077
  };
27722
28078
  BrowserListInputSchema = {
27723
- include_closed: import_zod31.z.boolean().default(false).describe("Include closed sessions in the list.")
28079
+ include_closed: import_zod32.z.boolean().default(false).describe("Include closed sessions in the list.")
27724
28080
  };
27725
28081
  BrowserCaptureFanoutInputSchema = {
27726
- session_id: import_zod31.z.string().describe("The session id returned by browser_open or browser_list_sessions. The session must be on chatgpt.com or claude.ai and logged in through a saved hosted profile. Use only a returned session_id; do not construct one yourself."),
27727
- prompt: import_zod31.z.string().optional().describe("Optional prompt to type into the chat composer and submit before capturing. Omit to passively capture the fan-out of a prompt the user just ran in the visible browser. The prompt must trigger web search for there to be a fan-out."),
27728
- wait_ms: import_zod31.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish before parsing. Defaults to 90000 when a prompt is sent, 8000 for passive capture. Capture ends as soon as the answer stream finishes."),
27729
- first_party_domain: import_zod31.z.string().optional().describe("The brand/site being researched, for example example.com. Sources on this domain are tagged First-party/vendor. Set this when the prompt is about a specific business."),
27730
- reset: import_zod31.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing. Use when re-running to avoid mixing turns."),
27731
- export: import_zod31.z.boolean().default(false).describe("When true, write JSON, CSV, TSV exports (queries, citations, all sources, browsed-only sources, snippets, domains) and a self-contained HTML report to MCP_SCRAPER_OUTPUT_DIR/fanout, returning paths relative to MCP_SCRAPER_OUTPUT_DIR.")
28082
+ session_id: import_zod32.z.string().describe("Session id from browser_open. Must be on chatgpt.com or claude.ai, logged in via a saved hosted profile."),
28083
+ prompt: import_zod32.z.string().optional().describe("Optional prompt to type and submit before capturing. Omit to passively capture a prompt the user just ran. Must trigger web search to produce a fan-out."),
28084
+ wait_ms: import_zod32.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish. Defaults to 90000 when a prompt is sent, 8000 for passive capture."),
28085
+ first_party_domain: import_zod32.z.string().optional().describe("The brand/site being researched, e.g. example.com \u2014 sources on this domain are tagged First-party/vendor."),
28086
+ reset: import_zod32.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing."),
28087
+ export: import_zod32.z.boolean().default(false).describe("Write JSON/CSV/TSV/HTML exports to MCP_SCRAPER_OUTPUT_DIR/fanout, returning relative paths.")
27732
28088
  };
27733
- FanoutSourceOutput = import_zod31.z.object({
27734
- url: import_zod31.z.string(),
27735
- domain: import_zod31.z.string(),
27736
- title: import_zod31.z.string(),
27737
- cited: import_zod31.z.boolean(),
27738
- timesCited: import_zod31.z.number().int().min(0),
27739
- snippet: import_zod31.z.string(),
27740
- round: import_zod31.z.number().int().nullable(),
27741
- siteType: import_zod31.z.string().describe("URL category: First-party/vendor, News/media, Reddit, Social/video, Encyclopedia, Review site, Docs, or Blog.")
28089
+ FanoutSourceOutput = import_zod32.z.object({
28090
+ url: import_zod32.z.string(),
28091
+ domain: import_zod32.z.string(),
28092
+ title: import_zod32.z.string(),
28093
+ cited: import_zod32.z.boolean(),
28094
+ timesCited: import_zod32.z.number().int().min(0),
28095
+ snippet: import_zod32.z.string(),
28096
+ round: import_zod32.z.number().int().nullable(),
28097
+ siteType: import_zod32.z.string().describe("URL category: First-party/vendor, News/media, Reddit, Social/video, Encyclopedia, Review site, Docs, or Blog.")
27742
28098
  });
27743
28099
  BrowserCaptureFanoutOutputSchema = {
27744
28100
  ...BrowserBaseOutput,
27745
- tool: import_zod31.z.literal("query_fanout_workflow"),
27746
- platform: import_zod31.z.enum(["ChatGPT", "Claude"]).describe("Which AI-search surface the fan-out was captured from."),
27747
- captured_at: import_zod31.z.string(),
27748
- prompt: import_zod31.z.string().describe("The user prompt that triggered the captured fan-out, when recoverable."),
27749
- meta: import_zod31.z.object({
27750
- model: import_zod31.z.string(),
27751
- finishType: import_zod31.z.string(),
27752
- title: import_zod31.z.string(),
27753
- rounds: import_zod31.z.number().int().min(0)
28101
+ tool: import_zod32.z.literal("query_fanout_workflow"),
28102
+ platform: import_zod32.z.enum(["ChatGPT", "Claude"]).describe("Which AI-search surface the fan-out was captured from."),
28103
+ captured_at: import_zod32.z.string(),
28104
+ prompt: import_zod32.z.string().describe("The user prompt that triggered the captured fan-out, when recoverable."),
28105
+ meta: import_zod32.z.object({
28106
+ model: import_zod32.z.string(),
28107
+ finishType: import_zod32.z.string(),
28108
+ title: import_zod32.z.string(),
28109
+ rounds: import_zod32.z.number().int().min(0)
27754
28110
  }),
27755
- queries: import_zod31.z.array(import_zod31.z.string()).describe("Every web-search sub-query the model issued, in capture order. Classify these by funnel stage and type yourself from the text."),
27756
- browsed_urls: import_zod31.z.array(FanoutSourceOutput).describe("Every researched URL (cited and browsed-only), cited first."),
27757
- cited_urls: import_zod31.z.array(FanoutSourceOutput).describe("The subset of researched URLs actually cited in the final answer."),
27758
- browsed_only: import_zod31.z.array(FanoutSourceOutput).describe("Researched URLs the model pulled but did not cite."),
27759
- snippets: import_zod31.z.array(import_zod31.z.object({ url: import_zod31.z.string(), domain: import_zod31.z.string(), title: import_zod31.z.string(), text: import_zod31.z.string() })),
27760
- counts: import_zod31.z.object({
27761
- subQueries: import_zod31.z.number().int().min(0),
27762
- browsed: import_zod31.z.number().int().min(0),
27763
- cited: import_zod31.z.number().int().min(0),
27764
- browsedOnly: import_zod31.z.number().int().min(0)
28111
+ queries: import_zod32.z.array(import_zod32.z.string()).describe("Every web-search sub-query issued, in capture order."),
28112
+ browsed_urls: import_zod32.z.array(FanoutSourceOutput).describe("Every researched URL, cited first."),
28113
+ cited_urls: import_zod32.z.array(FanoutSourceOutput).describe("Researched URLs cited in the final answer."),
28114
+ browsed_only: import_zod32.z.array(FanoutSourceOutput).describe("Researched URLs pulled but not cited."),
28115
+ snippets: import_zod32.z.array(import_zod32.z.object({ url: import_zod32.z.string(), domain: import_zod32.z.string(), title: import_zod32.z.string(), text: import_zod32.z.string() })),
28116
+ counts: import_zod32.z.object({
28117
+ subQueries: import_zod32.z.number().int().min(0),
28118
+ browsed: import_zod32.z.number().int().min(0),
28119
+ cited: import_zod32.z.number().int().min(0),
28120
+ browsedOnly: import_zod32.z.number().int().min(0)
27765
28121
  }),
27766
- aggregates: import_zod31.z.object({
27767
- topSites: import_zod31.z.array(import_zod31.z.object({ domain: import_zod31.z.string(), count: import_zod31.z.number().int(), cited: import_zod31.z.boolean(), timesCited: import_zod31.z.number().int(), siteType: import_zod31.z.string() })),
27768
- citationOrder: import_zod31.z.array(import_zod31.z.object({ rank: import_zod31.z.number().int(), domain: import_zod31.z.string(), url: import_zod31.z.string(), timesCited: import_zod31.z.number().int() })),
27769
- byCategory: import_zod31.z.record(import_zod31.z.number().int())
28122
+ aggregates: import_zod32.z.object({
28123
+ topSites: import_zod32.z.array(import_zod32.z.object({ domain: import_zod32.z.string(), count: import_zod32.z.number().int(), cited: import_zod32.z.boolean(), timesCited: import_zod32.z.number().int(), siteType: import_zod32.z.string() })),
28124
+ citationOrder: import_zod32.z.array(import_zod32.z.object({ rank: import_zod32.z.number().int(), domain: import_zod32.z.string(), url: import_zod32.z.string(), timesCited: import_zod32.z.number().int() })),
28125
+ byCategory: import_zod32.z.record(import_zod32.z.number().int())
27770
28126
  }).describe("Objective aggregates: top sourced sites by frequency, citation order, and URL-category counts."),
27771
- first_party_domain: import_zod31.z.string().nullable(),
27772
- exports: import_zod31.z.object({
27773
- relativeTo: import_zod31.z.string(),
27774
- dir: import_zod31.z.string(),
27775
- json: import_zod31.z.string(),
27776
- queriesCsv: import_zod31.z.string(),
27777
- queriesTsv: import_zod31.z.string(),
27778
- citationsCsv: import_zod31.z.string(),
27779
- sourcesCsv: import_zod31.z.string(),
27780
- browsedOnlyCsv: import_zod31.z.string(),
27781
- snippetsCsv: import_zod31.z.string(),
27782
- domainsCsv: import_zod31.z.string(),
27783
- report: import_zod31.z.string()
28127
+ first_party_domain: import_zod32.z.string().nullable(),
28128
+ exports: import_zod32.z.object({
28129
+ relativeTo: import_zod32.z.string(),
28130
+ dir: import_zod32.z.string(),
28131
+ json: import_zod32.z.string(),
28132
+ queriesCsv: import_zod32.z.string(),
28133
+ queriesTsv: import_zod32.z.string(),
28134
+ citationsCsv: import_zod32.z.string(),
28135
+ sourcesCsv: import_zod32.z.string(),
28136
+ browsedOnlyCsv: import_zod32.z.string(),
28137
+ snippetsCsv: import_zod32.z.string(),
28138
+ domainsCsv: import_zod32.z.string(),
28139
+ report: import_zod32.z.string()
27784
28140
  }).nullable().describe("Relative export paths when export=true, otherwise null. Paths are relative to MCP_SCRAPER_OUTPUT_DIR, or ~/Downloads/mcp-scraper when that env var is not set."),
27785
- debug: import_zod31.z.object({
27786
- interceptorReady: import_zod31.z.boolean(),
27787
- rawBytes: import_zod31.z.number().int().min(0),
27788
- unmappedKeys: import_zod31.z.array(import_zod31.z.string()),
27789
- note: import_zod31.z.string()
28141
+ debug: import_zod32.z.object({
28142
+ interceptorReady: import_zod32.z.boolean(),
28143
+ rawBytes: import_zod32.z.number().int().min(0),
28144
+ unmappedKeys: import_zod32.z.array(import_zod32.z.string()),
28145
+ note: import_zod32.z.string()
27790
28146
  }).optional()
27791
28147
  };
27792
28148
  BrowserOpenOutputSchema = {
27793
- ok: import_zod31.z.boolean(),
27794
- tool: import_zod31.z.literal("browser_open"),
27795
- session_id: import_zod31.z.string().describe("Session id returned by browser_open. Use only this exact value in later browser_* calls; do not construct one yourself."),
27796
- watch_url: import_zod31.z.string().describe("Human watch/takeover URL for this browser session on mcpscraper.dev."),
28149
+ ok: import_zod32.z.boolean(),
28150
+ tool: import_zod32.z.literal("browser_open"),
28151
+ session_id: import_zod32.z.string().describe("Session id returned by browser_open. Use only this exact value in later browser_* calls; do not construct one yourself."),
28152
+ watch_url: import_zod32.z.string().describe("Human watch/takeover URL for this browser session on mcpscraper.dev."),
27797
28153
  live_view_url: NullableString2.describe("Deprecated; always null. Open watch_url to view the live session."),
27798
28154
  url: NullableString2.describe("Initial URL requested by the caller, when provided."),
27799
- hint: import_zod31.z.string(),
28155
+ hint: import_zod32.z.string(),
27800
28156
  raw: BrowserRawObject.optional()
27801
28157
  };
27802
- BrowserProfileLogin = import_zod31.z.object({
28158
+ BrowserProfileLogin = import_zod32.z.object({
27803
28159
  connection_id: NullableString2.describe("Auth connection id for this login."),
27804
- domain: import_zod31.z.string().describe("Site this login is for, e.g. chatgpt.com."),
27805
- status: import_zod31.z.string().describe("Auth status, e.g. NEEDS_AUTH or AUTHENTICATED."),
28160
+ domain: import_zod32.z.string().describe("Site this login is for, e.g. chatgpt.com."),
28161
+ status: import_zod32.z.string().describe("Auth status, e.g. NEEDS_AUTH or AUTHENTICATED."),
27806
28162
  account_email: NullableString2.describe("Account email recorded for this login, when known."),
27807
28163
  note: NullableString2.describe("Free-text note describing this login."),
27808
28164
  watch_url: NullableString2.describe("mcpscraper.dev sign-in link when this login still needs the user to authenticate."),
27809
28165
  last_connected_at: NullableString2.describe("When this login was last saved or refreshed.")
27810
28166
  });
27811
28167
  BrowserProfileConnectOutputSchema = {
27812
- ok: import_zod31.z.boolean(),
27813
- tool: import_zod31.z.literal("browser_profile_connect"),
28168
+ ok: import_zod32.z.boolean(),
28169
+ tool: import_zod32.z.literal("browser_profile_connect"),
27814
28170
  session_id: NullableString2,
27815
- auth_connection_id: import_zod31.z.string(),
27816
- watch_url: import_zod31.z.string().describe("mcpscraper.dev sign-in link to give the user so they can complete this login."),
28171
+ auth_connection_id: import_zod32.z.string(),
28172
+ watch_url: import_zod32.z.string().describe("mcpscraper.dev sign-in link to give the user so they can complete this login."),
27817
28173
  live_view_url: NullableString2.describe("Deprecated; always null. Open watch_url to view the sign-in."),
27818
- profile: import_zod31.z.string().describe("Profile this login was added to. Reuse it to stack more logins or to open a session."),
27819
- domain: import_zod31.z.string(),
27820
- setup_url: import_zod31.z.string(),
28174
+ profile: import_zod32.z.string().describe("Profile this login was added to. Reuse it to stack more logins or to open a session."),
28175
+ domain: import_zod32.z.string(),
28176
+ setup_url: import_zod32.z.string(),
27821
28177
  account_email: NullableString2,
27822
28178
  note: NullableString2,
27823
- status: import_zod31.z.string(),
28179
+ status: import_zod32.z.string(),
27824
28180
  flow_status: NullableString2,
27825
28181
  flow_step: NullableString2,
27826
28182
  flow_expires_at: NullableString2,
27827
28183
  post_login_url: NullableString2,
27828
- connected_logins: import_zod31.z.array(BrowserProfileLogin).describe("Every login currently saved in this profile, so you can see what else is connected."),
27829
- next_steps: import_zod31.z.array(import_zod31.z.string()),
28184
+ connected_logins: import_zod32.z.array(BrowserProfileLogin).describe("Every login currently saved in this profile, so you can see what else is connected."),
28185
+ next_steps: import_zod32.z.array(import_zod32.z.string()),
27830
28186
  raw: BrowserRawObject.optional()
27831
28187
  };
27832
28188
  BrowserProfileListOutputSchema = {
27833
- ok: import_zod31.z.boolean(),
27834
- tool: import_zod31.z.literal("browser_profile_list"),
27835
- session_id: import_zod31.z.null(),
27836
- profile: import_zod31.z.string().describe("Profile these logins belong to."),
27837
- connections: import_zod31.z.array(BrowserProfileLogin).describe("All site logins saved in this profile, each with its current auth status and note."),
27838
- count: import_zod31.z.number().int().min(0)
28189
+ ok: import_zod32.z.boolean(),
28190
+ tool: import_zod32.z.literal("browser_profile_list"),
28191
+ session_id: import_zod32.z.null(),
28192
+ profile: import_zod32.z.string().describe("Profile these logins belong to."),
28193
+ connections: import_zod32.z.array(BrowserProfileLogin).describe("All site logins saved in this profile, each with its current auth status and note."),
28194
+ count: import_zod32.z.number().int().min(0)
27839
28195
  };
27840
28196
  BrowserScreenshotOutputSchema = {
27841
28197
  ...BrowserBaseOutput,
27842
- tool: import_zod31.z.literal("browser_screenshot"),
28198
+ tool: import_zod32.z.literal("browser_screenshot"),
27843
28199
  url: NullableString2,
27844
28200
  title: NullableString2,
27845
- text: import_zod31.z.string(),
27846
- elements: import_zod31.z.array(BrowserElementOutput),
27847
- screenshot: import_zod31.z.object({
27848
- mime_type: import_zod31.z.string(),
27849
- inline: import_zod31.z.boolean()
28201
+ text: import_zod32.z.string(),
28202
+ elements: import_zod32.z.array(BrowserElementOutput),
28203
+ screenshot: import_zod32.z.object({
28204
+ mime_type: import_zod32.z.string(),
28205
+ inline: import_zod32.z.boolean()
27850
28206
  }).nullable()
27851
28207
  };
27852
28208
  BrowserReadOutputSchema = {
27853
28209
  ...BrowserBaseOutput,
27854
- tool: import_zod31.z.literal("browser_read"),
28210
+ tool: import_zod32.z.literal("browser_read"),
27855
28211
  url: NullableString2,
27856
28212
  title: NullableString2,
27857
- text: import_zod31.z.string(),
27858
- elements: import_zod31.z.array(BrowserElementOutput),
28213
+ text: import_zod32.z.string(),
28214
+ elements: import_zod32.z.array(BrowserElementOutput),
27859
28215
  raw: BrowserRawObject.optional()
27860
28216
  };
27861
28217
  BrowserLocateOutputSchema = {
27862
28218
  ...BrowserBaseOutput,
27863
- tool: import_zod31.z.literal("browser_locate"),
28219
+ tool: import_zod32.z.literal("browser_locate"),
27864
28220
  url: NullableString2,
27865
28221
  title: NullableString2,
27866
28222
  viewport: BrowserRawObject.nullable(),
27867
28223
  replay: BrowserRawObject.nullable(),
27868
- targets: import_zod31.z.array(BrowserRawObject),
28224
+ targets: import_zod32.z.array(BrowserRawObject),
27869
28225
  raw: BrowserRawObject.optional()
27870
28226
  };
27871
28227
  BrowserActionOutputSchema = {
27872
28228
  ...BrowserBaseOutput,
27873
28229
  result: BrowserRawObject.describe("Provider action result. Check ok and follow with browser_screenshot/browser_read when page state matters."),
27874
- nextRecommendedTool: import_zod31.z.string().nullable()
28230
+ nextRecommendedTool: import_zod32.z.string().nullable()
27875
28231
  };
27876
28232
  BrowserReplayStartOutputSchema = {
27877
28233
  ...BrowserReplayBaseOutput,
27878
- tool: import_zod31.z.literal("browser_replay_start"),
28234
+ tool: import_zod32.z.literal("browser_replay_start"),
27879
28235
  view_url: NullableString2,
27880
28236
  download_url: NullableString2,
27881
28237
  raw: BrowserRawObject.optional()
27882
28238
  };
27883
28239
  BrowserReplayStopOutputSchema = {
27884
28240
  ...BrowserReplayBaseOutput,
27885
- tool: import_zod31.z.literal("browser_replay_stop"),
28241
+ tool: import_zod32.z.literal("browser_replay_stop"),
27886
28242
  view_url: NullableString2,
27887
28243
  download_url: NullableString2,
27888
28244
  raw: BrowserRawObject.optional()
27889
28245
  };
27890
28246
  BrowserListReplaysOutputSchema = {
27891
28247
  ...BrowserBaseOutput,
27892
- tool: import_zod31.z.literal("browser_list_replays"),
27893
- replays: import_zod31.z.array(BrowserRawObject),
27894
- count: import_zod31.z.number().int().min(0)
28248
+ tool: import_zod32.z.literal("browser_list_replays"),
28249
+ replays: import_zod32.z.array(BrowserRawObject),
28250
+ count: import_zod32.z.number().int().min(0)
27895
28251
  };
27896
28252
  BrowserReplayDownloadOutputSchema = {
27897
28253
  ...BrowserReplayBaseOutput,
27898
- tool: import_zod31.z.literal("browser_replay_download"),
28254
+ tool: import_zod32.z.literal("browser_replay_download"),
27899
28255
  file_path: NullableString2,
27900
- bytes: import_zod31.z.number().int().min(0).nullable(),
28256
+ bytes: import_zod32.z.number().int().min(0).nullable(),
27901
28257
  mime_type: NullableString2,
27902
28258
  download_url: NullableString2
27903
28259
  };
27904
28260
  BrowserReplayMarkOutputSchema = {
27905
28261
  ...BrowserReplayBaseOutput,
27906
- tool: import_zod31.z.literal("browser_replay_mark"),
28262
+ tool: import_zod32.z.literal("browser_replay_mark"),
27907
28263
  annotation: BrowserRawObject,
27908
- source_width: import_zod31.z.number().nullable(),
27909
- source_height: import_zod31.z.number().nullable(),
28264
+ source_width: import_zod32.z.number().nullable(),
28265
+ source_height: import_zod32.z.number().nullable(),
27910
28266
  target: BrowserRawObject.nullable(),
27911
- hint: import_zod31.z.string()
28267
+ hint: import_zod32.z.string()
27912
28268
  };
27913
28269
  BrowserReplayAnnotateOutputSchema = {
27914
28270
  ...BrowserReplayBaseOutput,
27915
- tool: import_zod31.z.literal("browser_replay_annotate"),
28271
+ tool: import_zod32.z.literal("browser_replay_annotate"),
27916
28272
  source_file_path: NullableString2,
27917
28273
  annotated_file_path: NullableString2,
27918
- bytes: import_zod31.z.number().int().min(0).nullable(),
27919
- width: import_zod31.z.number().int().min(0).nullable(),
27920
- height: import_zod31.z.number().int().min(0).nullable(),
27921
- annotation_count: import_zod31.z.number().int().min(0).nullable(),
28274
+ bytes: import_zod32.z.number().int().min(0).nullable(),
28275
+ width: import_zod32.z.number().int().min(0).nullable(),
28276
+ height: import_zod32.z.number().int().min(0).nullable(),
28277
+ annotation_count: import_zod32.z.number().int().min(0).nullable(),
27922
28278
  mime_type: NullableString2
27923
28279
  };
27924
28280
  BrowserCloseOutputSchema = {
27925
28281
  ...BrowserBaseOutput,
27926
- tool: import_zod31.z.literal("browser_close"),
27927
- closed: import_zod31.z.boolean(),
28282
+ tool: import_zod32.z.literal("browser_close"),
28283
+ closed: import_zod32.z.boolean(),
27928
28284
  raw: BrowserRawObject.optional()
27929
28285
  };
27930
28286
  BrowserListSessionsOutputSchema = {
27931
- ok: import_zod31.z.boolean(),
27932
- tool: import_zod31.z.literal("browser_list_sessions"),
27933
- session_id: import_zod31.z.null(),
27934
- sessions: import_zod31.z.array(BrowserRawObject),
27935
- count: import_zod31.z.number().int().min(0)
28287
+ ok: import_zod32.z.boolean(),
28288
+ tool: import_zod32.z.literal("browser_list_sessions"),
28289
+ session_id: import_zod32.z.null(),
28290
+ sessions: import_zod32.z.array(BrowserRawObject),
28291
+ count: import_zod32.z.number().int().min(0)
27936
28292
  };
27937
28293
  }
27938
28294
  });
@@ -28366,7 +28722,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28366
28722
  "browser_profile_connect",
28367
28723
  {
28368
28724
  title: "Save a Site Login to a Profile",
28369
- description: `Save a logged-in browser session so this server can reuse a site the user is signed into (ChatGPT, Claude, Reddit, or any account-gated site). Returns an mcpscraper.dev watch_url \u2014 give it to the user, they sign in once, and the cookies are saved to a named profile for later browser_open calls. ONE profile holds MANY logins: call this again with the SAME profile and a DIFFERENT domain to stack another account (e.g. add claude.ai to a profile that already has chatgpt.com). Pass note to record what each login is. Side effects: opens a short-lived sign-in session and persists cookies to the profile; it does NOT import the user's existing browser cookies (they log in fresh). NOT for one-off scraping of public pages (use extract_url) and NOT a way to drive the browser (use browser_open). Sample asks: "save my ChatGPT login", "connect my Reddit account", "add my Claude login to the same profile". After the user signs in, poll browser_profile_list until the login reads AUTHENTICATED, then browser_open with the profile.`,
28725
+ 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.",
28370
28726
  inputSchema: BrowserProfileConnectInputSchema,
28371
28727
  outputSchema: BrowserProfileConnectOutputSchema,
28372
28728
  annotations: annotations("Save a Site Login to a Profile")
@@ -28424,7 +28780,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28424
28780
  "browser_profile_list",
28425
28781
  {
28426
28782
  title: "List Saved Logins in a Profile",
28427
- description: `List every site login saved in a profile, each with its current auth status (NEEDS_AUTH / AUTHENTICATED), account email, and note. Use it to (1) see what a profile is connected to before opening a session, and (2) poll a just-saved login until it flips to AUTHENTICATED. Read-only, no cost. Pass profile (or email to derive it); narrow with domain or connection_id to poll a single login. Sample asks: "what's connected in my profile", "is my ChatGPT login ready yet", "which accounts are saved". Pairs with browser_profile_connect (which adds logins) and browser_open (which uses them).`,
28783
+ description: "List every site login saved in a profile with its auth status (NEEDS_AUTH/AUTHENTICATED), email, and note. Use to check what's connected, or to poll a just-saved login until AUTHENTICATED. Read-only, no cost. Pass profile (or email to derive it); narrow with domain or connection_id.",
28428
28784
  inputSchema: BrowserProfileListInputSchema,
28429
28785
  outputSchema: BrowserProfileListOutputSchema,
28430
28786
  annotations: annotations("List Saved Logins in a Profile", true)
@@ -28458,7 +28814,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28458
28814
  "browser_open",
28459
28815
  {
28460
28816
  title: "Open Browser Session",
28461
- description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session that is already logged into the sites saved in that profile (one profile can hold several logins). To set up or refresh a login first, use browser_profile_connect, then poll browser_profile_list until it reads AUTHENTICATED. Returns a session_id used by all other browser_* tools.",
28817
+ description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session already logged into that profile's sites (set one up first with browser_profile_connect). Returns a session_id used by all other browser_* tools.",
28462
28818
  inputSchema: BrowserOpenInputSchema,
28463
28819
  outputSchema: BrowserOpenOutputSchema,
28464
28820
  annotations: annotations("Open Browser Session")
@@ -28492,7 +28848,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28492
28848
  "browser_screenshot",
28493
28849
  {
28494
28850
  title: "See Page (Screenshot + Elements)",
28495
- description: "Capture what the browser currently shows. Returns a screenshot image PLUS a text snapshot listing interactive elements with their center x,y coordinates, the page url and title, and visible text. This is your primary way to perceive the page. Click elements by their listed x,y. If a Cloudflare/CAPTCHA challenge is visible, wait and screenshot again rather than clicking it.",
28851
+ description: "Capture what the browser currently shows: a screenshot plus a text snapshot of interactive elements with x,y coordinates, page url/title, and visible text. Primary way to perceive the page; click elements by their listed x,y. If a Cloudflare/CAPTCHA challenge is visible, wait and screenshot again rather than clicking it.",
28496
28852
  inputSchema: BrowserSessionInputSchema,
28497
28853
  outputSchema: BrowserScreenshotOutputSchema,
28498
28854
  annotations: annotations("See Page", true)
@@ -28524,7 +28880,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28524
28880
  "browser_read",
28525
28881
  {
28526
28882
  title: "Read Page Text + Elements",
28527
- description: "Return the page url, title, visible text, and the list of interactive elements (with x,y) without an image. Cheaper than browser_screenshot when you only need to read content or find a target element to click.",
28883
+ description: "Return the page url, title, visible text, and interactive elements (with x,y) without an image. Cheaper than browser_screenshot when you only need to read content or find a click target.",
28528
28884
  inputSchema: BrowserSessionInputSchema,
28529
28885
  outputSchema: BrowserReadOutputSchema,
28530
28886
  annotations: annotations("Read Page", true)
@@ -28548,7 +28904,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28548
28904
  "browser_locate",
28549
28905
  {
28550
28906
  title: "Locate DOM Targets",
28551
- description: "Locate exact visible DOM elements or text ranges in the current browser viewport and return left/top/width/height bounds in screenshot pixels. Use this before drawing annotations or when a callout must literally circle, box, underline, or point to a real element. Prefer CSS selectors for exact UI elements; use text when selector is unknown. When a replay is actively recording, the result includes replay_elapsed_seconds for timing.",
28907
+ description: "Locate exact visible DOM elements or text ranges and return left/top/width/height bounds in screenshot pixels. Use before drawing annotations that must circle, box, underline, or point to a real element. Prefer CSS selectors; use text when selector is unknown.",
28552
28908
  inputSchema: BrowserLocateInputSchema,
28553
28909
  outputSchema: BrowserLocateOutputSchema,
28554
28910
  annotations: annotations("Locate DOM Targets", true)
@@ -28573,7 +28929,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28573
28929
  "browser_goto",
28574
28930
  {
28575
28931
  title: "Navigate To URL",
28576
- description: "Navigate an existing browser session to a URL when the user asks you to go to another page or continue browsing in the same session. Use browser_open first if no session exists. Follow with browser_screenshot to see redirects, login walls, CAPTCHA/Cloudflare state, or the loaded page.",
28932
+ description: "Navigate an existing browser session to a URL. Use browser_open first if no session exists; follow with browser_screenshot to see the loaded page.",
28577
28933
  inputSchema: BrowserGotoInputSchema,
28578
28934
  outputSchema: BrowserActionOutputSchema,
28579
28935
  annotations: annotations("Navigate To URL")
@@ -28587,7 +28943,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28587
28943
  "browser_click",
28588
28944
  {
28589
28945
  title: "Click",
28590
- description: "Click a visible page target using screenshot pixel coordinates. Use this when the user asks you to press a button, open a menu, choose a result, or activate a visible UI element. Use x/y only from the latest browser_screenshot, browser_read, or browser_locate result; do not guess coordinates. Follow with browser_screenshot when the click may change the page.",
28946
+ description: "Click a visible page target using screenshot pixel coordinates. Use x/y only from the latest browser_screenshot, browser_read, or browser_locate result; do not guess coordinates.",
28591
28947
  inputSchema: BrowserClickInputSchema,
28592
28948
  outputSchema: BrowserActionOutputSchema,
28593
28949
  annotations: annotations("Click")
@@ -28606,7 +28962,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28606
28962
  "browser_type",
28607
28963
  {
28608
28964
  title: "Type Text",
28609
- description: 'Type text into the currently focused browser field. Use this when the user asks you to fill a search box, form field, login field, or editable text area. Click or Tab to the field first if focus is uncertain. Use browser_press with ["Return"] to submit, and browser_screenshot afterward when page state matters.',
28965
+ description: 'Type text into the currently focused browser field. Click or Tab to the field first if focus is uncertain. Use browser_press with ["Return"] to submit.',
28610
28966
  inputSchema: BrowserTypeInputSchema,
28611
28967
  outputSchema: BrowserActionOutputSchema,
28612
28968
  annotations: annotations("Type Text")
@@ -28620,7 +28976,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28620
28976
  "browser_scroll",
28621
28977
  {
28622
28978
  title: "Scroll",
28623
- description: "Scroll the page to reveal more content before reading, clicking, or locating elements. Positive delta_y scrolls down; negative delta_y scrolls up. Follow with browser_screenshot or browser_read to inspect newly revealed content.",
28979
+ description: "Scroll the page to reveal more content. Positive delta_y scrolls down; negative scrolls up.",
28624
28980
  inputSchema: BrowserScrollInputSchema,
28625
28981
  outputSchema: BrowserActionOutputSchema,
28626
28982
  annotations: annotations("Scroll")
@@ -28639,7 +28995,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28639
28995
  "browser_press",
28640
28996
  {
28641
28997
  title: "Press Keys",
28642
- description: 'Press keyboard keys or combinations in the active browser session. Use this for submit, Escape, Tab navigation, select-all, or keyboard shortcuts. Examples: ["Return"], ["Escape"], ["Tab"], ["Ctrl+a"], ["Ctrl+Shift+Tab"]. Use browser_type for text entry and browser_screenshot after keypresses that may change the page.',
28998
+ description: "Press keyboard keys or combinations in the active browser session \u2014 submit, Escape, Tab navigation, select-all, or shortcuts. Use browser_type for text entry.",
28643
28999
  inputSchema: BrowserPressInputSchema,
28644
29000
  outputSchema: BrowserActionOutputSchema,
28645
29001
  annotations: annotations("Press Keys")
@@ -28653,7 +29009,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28653
29009
  "browser_replay_start",
28654
29010
  {
28655
29011
  title: "Start Recording",
28656
- description: "Start recording an MP4 replay of the session. Returns replay_id, view_url when available, and a download_url. Use to capture a task for later review; stop with browser_replay_stop.",
29012
+ description: "Start recording an MP4 replay of the session. Returns replay_id and a download_url. Stop with browser_replay_stop.",
28657
29013
  inputSchema: BrowserSessionInputSchema,
28658
29014
  outputSchema: BrowserReplayStartOutputSchema,
28659
29015
  annotations: annotations("Start Recording")
@@ -28676,7 +29032,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28676
29032
  "browser_replay_stop",
28677
29033
  {
28678
29034
  title: "Stop Recording",
28679
- description: "Stop a replay recording and expose the final view_url and download_url. Use browser_replay_download to save the MP4 to the configured output directory.",
29035
+ description: "Stop a replay recording and expose its final view_url/download_url. Use browser_replay_download to save the MP4.",
28680
29036
  inputSchema: BrowserReplayStopInputSchema,
28681
29037
  outputSchema: BrowserReplayStopOutputSchema,
28682
29038
  annotations: annotations("Stop Recording")
@@ -28699,7 +29055,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28699
29055
  "browser_list_replays",
28700
29056
  {
28701
29057
  title: "List Replay Videos",
28702
- description: "List replay recordings for a browser session, including final view_url and authenticated download_url values when available.",
29058
+ description: "List replay recordings for a browser session, including view_url and download_url when available.",
28703
29059
  inputSchema: BrowserSessionInputSchema,
28704
29060
  outputSchema: BrowserListReplaysOutputSchema,
28705
29061
  annotations: annotations("List Replay Videos", true)
@@ -28721,7 +29077,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28721
29077
  "browser_replay_download",
28722
29078
  {
28723
29079
  title: "Download Replay MP4",
28724
- description: "Download a replay recording through MCP Scraper and save the MP4 under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use after browser_replay_stop or browser_list_replays.",
29080
+ description: "Download a replay recording and save the MP4 under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use after browser_replay_stop or browser_list_replays.",
28725
29081
  inputSchema: BrowserReplayDownloadInputSchema,
28726
29082
  outputSchema: BrowserReplayDownloadOutputSchema,
28727
29083
  annotations: annotations("Download Replay MP4")
@@ -28745,7 +29101,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28745
29101
  "browser_replay_mark",
28746
29102
  {
28747
29103
  title: "Mark Replay Annotation",
28748
- description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation object with DOM bounds and replay-relative timing. Use this instead of guessing start_seconds or drawing rough rectangles. Workflow: start browser_replay_start, navigate until the target is visible and stable, call browser_replay_mark for each callout, then stop the replay and pass the returned annotations to browser_replay_annotate.",
29104
+ description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation with DOM bounds and replay-relative timing, instead of guessing start_seconds or rectangles. Pass the returned annotations to browser_replay_annotate after stopping the replay.",
28749
29105
  inputSchema: BrowserReplayMarkInputSchema,
28750
29106
  outputSchema: BrowserReplayMarkOutputSchema,
28751
29107
  annotations: annotations("Mark Replay Annotation", true)
@@ -28795,7 +29151,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28795
29151
  "browser_replay_annotate",
28796
29152
  {
28797
29153
  title: "Annotate Replay MP4",
28798
- description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4 under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use this after browser_replay_stop when the user wants proof videos with circles, boxes, arrows, underlines, or labels. For accurate timing and placement, prefer annotations returned by browser_replay_mark while the replay is recording; otherwise use exact left/top/width/height bounds from browser_locate. If the replay video size differs from the screenshot coordinate space, pass source_width and source_height.",
29154
+ description: "Download a browser replay MP4, render visual annotations (circles/boxes/arrows/labels) over it, and save a new annotated MP4. Prefer annotations from browser_replay_mark for accurate timing; otherwise use exact bounds from browser_locate. Pass source_width/source_height if the replay video size differs from the screenshot coordinate space.",
28799
29155
  inputSchema: BrowserReplayAnnotateInputSchema,
28800
29156
  outputSchema: BrowserReplayAnnotateOutputSchema,
28801
29157
  annotations: annotations("Annotate Replay MP4")
@@ -28837,7 +29193,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28837
29193
  "browser_close",
28838
29194
  {
28839
29195
  title: "Close Browser Session",
28840
- description: "Close and release a browser session when the task is done, when the user asks to stop the browser, or when active browser billing should end. Use browser_list_sessions first if you need to recover the session_id.",
29196
+ description: "Close and release a browser session when the task is done, to end active browser billing. Use browser_list_sessions first to recover a session_id.",
28841
29197
  inputSchema: BrowserSessionInputSchema,
28842
29198
  outputSchema: BrowserCloseOutputSchema,
28843
29199
  annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
@@ -28858,7 +29214,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28858
29214
  "browser_list_sessions",
28859
29215
  {
28860
29216
  title: "List Browser Sessions",
28861
- description: "List browser sessions and their status, with a watch_url for each. Use this to recover a session_id, find an active browser, or decide which session to close. Use browser_open to create a new session.",
29217
+ description: "List browser sessions and their status, with a watch_url for each. Use to recover a session_id or decide which session to close.",
28862
29218
  inputSchema: BrowserListInputSchema,
28863
29219
  outputSchema: BrowserListSessionsOutputSchema,
28864
29220
  annotations: annotations("List Browser Sessions", true)
@@ -28880,7 +29236,7 @@ function registerBrowserAgentMcpTools(server, opts) {
28880
29236
  "query_fanout_workflow",
28881
29237
  {
28882
29238
  title: "Capture AI Search Fan-Out",
28883
- description: `Capture the query fan-out behind a ChatGPT or Claude web-search answer for Answer Engine Optimization (AEO): the exact sub-queries the model issued, every researched URL split into cited vs browsed-only (with citation frequency and snippet), each source tagged by category (First-party/vendor, News/media, Reddit, Social/video, Encyclopedia, Review site, Docs, Blog), plus top sourced sites and citation order. Set export=true for durable JSON, CSV, TSV, and HTML artifacts; export paths are returned relative to MCP_SCRAPER_OUTPUT_DIR. This returns the raw structured data; YOU analyze it \u2014 classify each sub-query by funnel stage (Problem-aware, Solution-aware, Decision-aware, Retention) and type (BoFu, Branded, Comparison, How-to, Search operator), name the brands the model researched, and give AEO insights. Sample asks: "capture the fan-out for this ChatGPT answer", "what did Claude search and cite for best CRM", "show the sub-queries and sources behind this AI answer". WRITE NOTE: passing prompt submits a real message in the user's logged-in account (a new conversation turn) \u2014 only send when the user wants that; omit prompt to capture a prompt the user just ran. Setup, if not already connected or if the profile disconnected: call browser_profile_connect, give the user the watch_url, let them complete login, then poll browser_profile_list until status is AUTHENTICATED. After that, browser_open the saved direct no-proxy profile and go to chatgpt.com or claude.ai. Fan-out is captured only as it streams, so the session must be open when the prompt runs. NOT for Google AI Overview citations \u2014 use harvest_paa for those; this tool is ChatGPT and Claude only.`,
29239
+ description: "Capture the query fan-out behind a ChatGPT or Claude web-search answer for AEO: sub-queries issued, every researched URL split into cited vs browsed-only, and top sourced sites. Returns raw structured data for you to classify and analyze. Set export=true for JSON/CSV/TSV/HTML artifacts. WRITE NOTE: passing prompt submits a real message in the user's logged-in account \u2014 only send when the user wants that; omit it to capture a prompt the user just ran. The session must already be open on chatgpt.com or claude.ai (see browser_profile_connect) while the prompt streams. NOT for Google AI Overview \u2014 use harvest_paa for that.",
28884
29240
  inputSchema: BrowserCaptureFanoutInputSchema,
28885
29241
  outputSchema: BrowserCaptureFanoutOutputSchema,
28886
29242
  annotations: annotations("Capture AI Search Fan-Out")
@@ -29036,11 +29392,11 @@ async function requireMcpCallerKey(c) {
29036
29392
  if (!user) return mcpAuthError();
29037
29393
  return callerKey;
29038
29394
  }
29039
- var import_hono12, import_webStandardStreamableHttp, mcpApp;
29395
+ var import_hono14, import_webStandardStreamableHttp, mcpApp;
29040
29396
  var init_mcp_routes = __esm({
29041
29397
  "src/mcp/mcp-routes.ts"() {
29042
29398
  "use strict";
29043
- import_hono12 = require("hono");
29399
+ import_hono14 = require("hono");
29044
29400
  import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
29045
29401
  init_paa_mcp_server();
29046
29402
  init_http_mcp_tool_executor();
@@ -29049,7 +29405,7 @@ var init_mcp_routes = __esm({
29049
29405
  init_db();
29050
29406
  init_mcp_oauth();
29051
29407
  configureReportSaving(false);
29052
- mcpApp = new import_hono12.Hono();
29408
+ mcpApp = new import_hono14.Hono();
29053
29409
  mcpApp.all("/", async (c) => {
29054
29410
  try {
29055
29411
  const keyOrError = await requireMcpCallerKey(c);
@@ -29185,7 +29541,7 @@ async function listAuthConnectionsByProfile(profile) {
29185
29541
  }
29186
29542
  async function createSessionRow(input) {
29187
29543
  const db = getDb();
29188
- const id = `bas_${(0, import_node_crypto7.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
29544
+ const id = `bas_${(0, import_node_crypto9.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
29189
29545
  await db.execute({
29190
29546
  sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
29191
29547
  VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
@@ -29254,7 +29610,7 @@ async function recordAction(input) {
29254
29610
  sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
29255
29611
  VALUES (?, ?, ?, ?, ?, ?)`,
29256
29612
  args: [
29257
- `baa_${(0, import_node_crypto7.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
29613
+ `baa_${(0, import_node_crypto9.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
29258
29614
  input.sessionId,
29259
29615
  input.type,
29260
29616
  input.params == null ? null : JSON.stringify(input.params),
@@ -29294,11 +29650,11 @@ async function listReplayRows(sessionId) {
29294
29650
  });
29295
29651
  return res.rows;
29296
29652
  }
29297
- var import_node_crypto7, _ready2;
29653
+ var import_node_crypto9, _ready2;
29298
29654
  var init_browser_agent_db = __esm({
29299
29655
  "src/api/browser-agent-db.ts"() {
29300
29656
  "use strict";
29301
- import_node_crypto7 = require("crypto");
29657
+ import_node_crypto9 = require("crypto");
29302
29658
  init_db();
29303
29659
  _ready2 = false;
29304
29660
  }
@@ -30573,7 +30929,7 @@ async function loadOpenSession(id, userId) {
30573
30929
  return row;
30574
30930
  }
30575
30931
  function buildBrowserAgentRoutes() {
30576
- const app2 = new import_hono13.Hono();
30932
+ const app2 = new import_hono15.Hono();
30577
30933
  app2.use("*", async (c, next) => {
30578
30934
  await migrateBrowserAgent();
30579
30935
  return next();
@@ -31035,11 +31391,11 @@ function buildBrowserAgentRoutes() {
31035
31391
  });
31036
31392
  return app2;
31037
31393
  }
31038
- var import_hono13, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS;
31394
+ var import_hono15, auth, DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS;
31039
31395
  var init_browser_agent_routes = __esm({
31040
31396
  "src/api/browser-agent-routes.ts"() {
31041
31397
  "use strict";
31042
- import_hono13 = require("hono");
31398
+ import_hono15 = require("hono");
31043
31399
  init_api_auth();
31044
31400
  init_errors();
31045
31401
  init_db();
@@ -31299,168 +31655,6 @@ var init_browser_agent_console = __esm({
31299
31655
  }
31300
31656
  });
31301
31657
 
31302
- // src/api/session.ts
31303
- function getSessionSecret() {
31304
- const configured = process.env.SESSION_SECRET?.trim();
31305
- if (configured) return configured;
31306
- if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
31307
- return "dev-secret-change-me";
31308
- }
31309
- function safeEqualHex(a, b) {
31310
- if (a.length !== b.length) return false;
31311
- try {
31312
- return (0, import_node_crypto8.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
31313
- } catch {
31314
- return false;
31315
- }
31316
- }
31317
- function signSession(userId) {
31318
- const payload = String(userId);
31319
- const sig = (0, import_node_crypto8.createHmac)("sha256", secret()).update(payload).digest("hex");
31320
- return `${payload}.${sig}`;
31321
- }
31322
- function verifySession(token) {
31323
- const dot = token.lastIndexOf(".");
31324
- if (dot === -1) return null;
31325
- const payload = token.slice(0, dot);
31326
- const sig = token.slice(dot + 1);
31327
- const expected = (0, import_node_crypto8.createHmac)("sha256", secret()).update(payload).digest("hex");
31328
- if (!safeEqualHex(sig, expected)) return null;
31329
- const id = parseInt(payload);
31330
- return isNaN(id) ? null : id;
31331
- }
31332
- var import_node_crypto8, isProduction, secret;
31333
- var init_session = __esm({
31334
- "src/api/session.ts"() {
31335
- "use strict";
31336
- import_node_crypto8 = require("crypto");
31337
- isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
31338
- secret = () => getSessionSecret();
31339
- }
31340
- });
31341
-
31342
- // src/api/memory.ts
31343
- function isMemoryOperator(email) {
31344
- if (!email) return false;
31345
- const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
31346
- return ops.includes(email.trim().toLowerCase());
31347
- }
31348
- function encKey() {
31349
- return (0, import_node_crypto9.scryptSync)(getSessionSecret(), "mcp-memory-key-v1", 32);
31350
- }
31351
- function encryptMemoryKey(secret2) {
31352
- const iv = (0, import_node_crypto9.randomBytes)(12);
31353
- const cipher = (0, import_node_crypto9.createCipheriv)("aes-256-gcm", encKey(), iv);
31354
- const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
31355
- const tag = cipher.getAuthTag();
31356
- return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
31357
- }
31358
- function decryptMemoryKey(stored) {
31359
- try {
31360
- const [ivB, tagB, dataB] = stored.split(":");
31361
- const decipher = (0, import_node_crypto9.createDecipheriv)("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
31362
- decipher.setAuthTag(Buffer.from(tagB, "base64"));
31363
- return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
31364
- } catch {
31365
- return null;
31366
- }
31367
- }
31368
- async function memoryCall(toolName, args, userMemoryKey) {
31369
- try {
31370
- const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
31371
- method: "POST",
31372
- headers: { "content-type": "application/json" },
31373
- body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
31374
- });
31375
- const body = await res.json().catch(() => null);
31376
- if (!res.ok) return { ok: false, error: body?.error ?? `memory ${toolName} failed (${res.status})` };
31377
- if (body?.result) return body.result;
31378
- return { ok: false, error: body?.error ?? `memory ${toolName} returned no result` };
31379
- } catch (err) {
31380
- return { ok: false, error: err?.message ?? "memory call failed" };
31381
- }
31382
- }
31383
- function personalVault(user) {
31384
- return `mcp-${user.id}`;
31385
- }
31386
- function memoryIdentity(user) {
31387
- return user.email;
31388
- }
31389
- function resolveMemoryIdentity(user) {
31390
- return memoryIdentity(user);
31391
- }
31392
- function memoryPlanForReads(user) {
31393
- if (isMemoryOperator(user.email)) return "unlimited";
31394
- if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
31395
- return "free";
31396
- }
31397
- async function provisionMemoryForUser(user) {
31398
- try {
31399
- const { key } = await getOrCreateUserMemoryKey(user);
31400
- if (key) await setMemoryProvisioned(user.id, true);
31401
- } catch {
31402
- }
31403
- }
31404
- async function getOrCreateUserMemoryKey(user) {
31405
- const creds = await getUserMemoryCreds(user.id);
31406
- if (creds.memory_key) {
31407
- const decrypted = decryptMemoryKey(creds.memory_key);
31408
- if (decrypted) return { key: decrypted };
31409
- }
31410
- const admin = ADMIN_KEY();
31411
- if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
31412
- const identity = memoryIdentity(user);
31413
- const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
31414
- const provisioned = await memoryCall(
31415
- "provisionDefaultsTool",
31416
- { granteeIdentity: identity, issueKey: true, plan },
31417
- admin
31418
- );
31419
- if (!provisioned.ok || !provisioned.secret) {
31420
- return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
31421
- }
31422
- await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
31423
- return { key: provisioned.secret };
31424
- }
31425
- async function syncMemoryKeyPlan(user, plan) {
31426
- const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
31427
- const { key, error } = await getOrCreateUserMemoryKey(user);
31428
- if (!key) return { ok: false, error: error ?? "memory unavailable" };
31429
- const listed = await memoryCall(
31430
- "listKeysTool",
31431
- {},
31432
- key
31433
- );
31434
- if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
31435
- const target = listed.keys[0];
31436
- if (target.plan === effectivePlan) return { ok: true };
31437
- const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
31438
- return { ok: res.ok, error: res.error };
31439
- }
31440
- async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
31441
- const admin = ADMIN_KEY();
31442
- if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
31443
- const identity = resolveMemoryIdentity(user);
31444
- const res = await memoryCall("setScheduleEntitlementTool", {
31445
- granteeIdentity: identity,
31446
- enabled,
31447
- quotaPerPeriod,
31448
- mcpScraperApiKey: user.api_key
31449
- }, admin);
31450
- return { ok: res.ok, error: res.error };
31451
- }
31452
- var import_node_crypto9, MEMORY_BASE_URL, ADMIN_KEY;
31453
- var init_memory = __esm({
31454
- "src/api/memory.ts"() {
31455
- "use strict";
31456
- import_node_crypto9 = require("crypto");
31457
- init_session();
31458
- init_db();
31459
- MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://mcp-memory-omega.vercel.app").replace(/\/$/, "");
31460
- ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
31461
- }
31462
- });
31463
-
31464
31658
  // src/api/stripe-routes.ts
31465
31659
  function linePriceId(line) {
31466
31660
  const l = line;
@@ -31474,17 +31668,17 @@ async function resolveUser(customerId, emailFallback) {
31474
31668
  if (user) await setStripeCustomerId(user.id, customerId);
31475
31669
  return user;
31476
31670
  }
31477
- var import_stripe, import_hono14, stripe, stripeApp;
31671
+ var import_stripe, import_hono16, stripe, stripeApp;
31478
31672
  var init_stripe_routes = __esm({
31479
31673
  "src/api/stripe-routes.ts"() {
31480
31674
  "use strict";
31481
31675
  import_stripe = __toESM(require("stripe"), 1);
31482
- import_hono14 = require("hono");
31676
+ import_hono16 = require("hono");
31483
31677
  init_db();
31484
31678
  init_rates();
31485
31679
  init_memory();
31486
31680
  stripe = new import_stripe.default(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
31487
- stripeApp = new import_hono14.Hono();
31681
+ stripeApp = new import_hono16.Hono();
31488
31682
  stripeApp.post("/webhooks", async (c) => {
31489
31683
  const sig = c.req.header("stripe-signature");
31490
31684
  const body = await c.req.text();
@@ -31775,11 +31969,11 @@ async function mintAccessToken(identity, scope, plan, audience) {
31775
31969
  function tokenErrorResponse(c, error, description, status) {
31776
31970
  return c.json({ error, error_description: description }, status);
31777
31971
  }
31778
- var import_hono15, import_cookie, import_node_crypto10, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
31972
+ var import_hono17, import_cookie, import_node_crypto10, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
31779
31973
  var init_oauth_routes = __esm({
31780
31974
  "src/api/oauth-routes.ts"() {
31781
31975
  "use strict";
31782
- import_hono15 = require("hono");
31976
+ import_hono17 = require("hono");
31783
31977
  import_cookie = require("hono/cookie");
31784
31978
  import_node_crypto10 = require("crypto");
31785
31979
  import_jose2 = require("jose");
@@ -31796,6 +31990,7 @@ var init_oauth_routes = __esm({
31796
31990
  ACCESS_TTL_SECONDS = 3600;
31797
31991
  REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
31798
31992
  CODE_TTL_SECONDS = 60;
31993
+ ROTATION_GRACE_SECONDS = 120;
31799
31994
  secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
31800
31995
  sessionCookieOptions = {
31801
31996
  httpOnly: true,
@@ -31805,7 +32000,7 @@ var init_oauth_routes = __esm({
31805
32000
  sameSite: "Lax"
31806
32001
  };
31807
32002
  cachedKeys = null;
31808
- oauthApp = new import_hono15.Hono();
32003
+ oauthApp = new import_hono17.Hono();
31809
32004
  oauthApp.use("*", async (c, next) => {
31810
32005
  c.header("Access-Control-Allow-Origin", "*");
31811
32006
  c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -31974,6 +32169,13 @@ var init_oauth_routes = __esm({
31974
32169
  const refreshToken = get("refresh_token") ?? "";
31975
32170
  const clientId = get("client_id") ?? "";
31976
32171
  const record = await getRefresh(refreshToken);
32172
+ if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
32173
+ const graceUser = await getUserByEmail(record.identity);
32174
+ const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
32175
+ const graceAudience = record.resource ?? RESOURCE();
32176
+ const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
32177
+ return c.json({ access_token: graceAccess, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: record.replaced_by, scope: record.scope });
32178
+ }
31977
32179
  if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
31978
32180
  if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
31979
32181
  if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
@@ -32543,13 +32745,13 @@ loadChannels();
32543
32745
  </body>
32544
32746
  </html>`;
32545
32747
  }
32546
- var import_hono16, chatApp;
32748
+ var import_hono18, chatApp;
32547
32749
  var init_chat_routes = __esm({
32548
32750
  "src/api/chat-routes.ts"() {
32549
32751
  "use strict";
32550
- import_hono16 = require("hono");
32752
+ import_hono18 = require("hono");
32551
32753
  init_memory();
32552
- chatApp = new import_hono16.Hono();
32754
+ chatApp = new import_hono18.Hono();
32553
32755
  chatApp.get("/api/channels", async (c) => {
32554
32756
  const token = c.req.query("token");
32555
32757
  if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
@@ -32939,13 +33141,13 @@ loadActions();
32939
33141
  </body>
32940
33142
  </html>`;
32941
33143
  }
32942
- var import_hono17, scheduleApp;
33144
+ var import_hono19, scheduleApp;
32943
33145
  var init_schedule_routes = __esm({
32944
33146
  "src/api/schedule-routes.ts"() {
32945
33147
  "use strict";
32946
- import_hono17 = require("hono");
33148
+ import_hono19 = require("hono");
32947
33149
  init_memory();
32948
- scheduleApp = new import_hono17.Hono();
33150
+ scheduleApp = new import_hono19.Hono();
32949
33151
  scheduleApp.get("/api/status", async (c) => {
32950
33152
  const token = c.req.query("token");
32951
33153
  if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
@@ -33636,6 +33838,7 @@ function opForCostPath(p) {
33636
33838
  if (p.startsWith("/instagram/")) return "instagram";
33637
33839
  if (p === "/reddit/thread") return "reddit_thread";
33638
33840
  if (p.startsWith("/api/internal/site-architecture-auditor")) return "audit_site";
33841
+ if (p.startsWith("/api/internal/memory")) return "memory_ai";
33639
33842
  if (p.startsWith("/agent/")) return "browser_session";
33640
33843
  return null;
33641
33844
  }
@@ -33689,7 +33892,7 @@ async function checkHarvestLimits(user, reuseLockId) {
33689
33892
  }
33690
33893
  return null;
33691
33894
  }
33692
- var import_resend, import_hono18, import_hono19, import_factory6, import_cookie2, import_stripe2, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, app, STRIPE_API_VERSION, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
33895
+ var import_resend, import_hono20, import_hono21, import_factory6, import_cookie2, import_stripe2, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, app, STRIPE_API_VERSION, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
33693
33896
  var init_server = __esm({
33694
33897
  "src/api/server.ts"() {
33695
33898
  "use strict";
@@ -33707,20 +33910,22 @@ var init_server = __esm({
33707
33910
  init_media_extractor();
33708
33911
  init_site_mapper();
33709
33912
  init_site_extractor();
33710
- import_hono18 = require("hono");
33711
- import_hono19 = require("inngest/hono");
33913
+ import_hono20 = require("hono");
33914
+ import_hono21 = require("inngest/hono");
33712
33915
  init_client();
33713
33916
  init_site_audit();
33714
33917
  init_site_extract();
33715
33918
  init_site_extract_repository();
33716
33919
  init_catalog_seed();
33717
33920
  init_site_audit_routes();
33921
+ init_internal_memory_routes();
33718
33922
  init_youtube_routes();
33719
33923
  init_screenshot_routes();
33720
33924
  init_facebook_ad_routes();
33721
33925
  init_google_ads_routes();
33722
33926
  init_instagram_routes();
33723
33927
  init_reddit_routes();
33928
+ init_video_routes();
33724
33929
  init_maps_routes();
33725
33930
  init_directory_routes();
33726
33931
  init_workflow_routes();
@@ -33797,7 +34002,7 @@ var init_server = __esm({
33797
34002
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
33798
34003
  return next();
33799
34004
  });
33800
- app = new import_hono18.Hono();
34005
+ app = new import_hono20.Hono();
33801
34006
  STRIPE_API_VERSION = "2026-02-25.clover";
33802
34007
  app.use("*", async (c, next) => {
33803
34008
  await next();
@@ -33879,6 +34084,27 @@ var init_server = __esm({
33879
34084
  (0, import_cookie2.deleteCookie)(c, "session", { path: "/", secure: secureCookies2, sameSite: "Strict" });
33880
34085
  return c.json({ ok: true });
33881
34086
  });
34087
+ app.post("/account/delete", requireAllowedOrigin, sessionAuth, async (c) => {
34088
+ const user = c.get("sessionUser");
34089
+ const secret2 = requireStripeSecret();
34090
+ if (secret2 && user.stripe_customer_id) {
34091
+ const stripeClient = new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
34092
+ const subs = await stripeClient.subscriptions.list({ customer: user.stripe_customer_id, status: "all", limit: 100 });
34093
+ for (const sub of subs.data) {
34094
+ if (sub.status === "active" || sub.status === "trialing" || sub.status === "past_due") {
34095
+ try {
34096
+ await stripeClient.subscriptions.cancel(sub.id);
34097
+ } catch (err) {
34098
+ console.error("[account/delete] failed to cancel subscription", sub.id, err instanceof Error ? err.message : String(err));
34099
+ return c.json({ error: "Could not cancel an active subscription \u2014 please try again or contact support." }, 500);
34100
+ }
34101
+ }
34102
+ }
34103
+ }
34104
+ await deactivateUser(user.id);
34105
+ (0, import_cookie2.deleteCookie)(c, "session", { path: "/", secure: secureCookies2, sameSite: "Strict" });
34106
+ return c.json({ ok: true });
34107
+ });
33882
34108
  app.post("/auth/forgot-password", requireAllowedOrigin, async (c) => {
33883
34109
  const { email } = await c.req.json();
33884
34110
  const normalizedEmail = email?.trim().toLowerCase();
@@ -34851,6 +35077,24 @@ var init_server = __esm({
34851
35077
  return c.json({ error: message }, 500);
34852
35078
  }
34853
35079
  });
35080
+ app.post("/billing/portal", requireAllowedOrigin, sessionAuth, async (c) => {
35081
+ try {
35082
+ const user = c.get("sessionUser");
35083
+ if (!user.stripe_customer_id) return c.json({ error: "No billing account yet \u2014 subscribe first." }, 409);
35084
+ const secret2 = requireStripeSecret();
35085
+ if (!secret2) return c.json({ error: "Stripe is not configured." }, 503);
35086
+ const stripeClient = new import_stripe2.default(secret2, { apiVersion: STRIPE_API_VERSION });
35087
+ const session = await stripeClient.billingPortal.sessions.create({
35088
+ customer: user.stripe_customer_id,
35089
+ return_url: `${appOrigin()}/billing`
35090
+ });
35091
+ return c.json({ url: session.url });
35092
+ } catch (err) {
35093
+ const message = err instanceof Error ? err.message : "Unable to open billing portal.";
35094
+ console.error("[billing/portal]", message);
35095
+ return c.json({ error: message }, 500);
35096
+ }
35097
+ });
34854
35098
  app.post("/billing/concurrency/terminal-checkout", auth2, async (c) => {
34855
35099
  try {
34856
35100
  const user = c.get("user");
@@ -35031,14 +35275,16 @@ var init_server = __esm({
35031
35275
  ]);
35032
35276
  return c.json({ drained: results.length, results, sweepResult, reaped: reapResult, expired: expiredResult, blobCleanup, workflowDispatch: workflowDispatchResult });
35033
35277
  });
35034
- app.on(["GET", "POST", "PUT"], "/api/inngest", (0, import_hono19.serve)({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
35278
+ app.on(["GET", "POST", "PUT"], "/api/inngest", (0, import_hono21.serve)({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
35035
35279
  app.route("/api/internal/site-architecture-auditor", siteAuditApp);
35280
+ app.route("/api/internal/memory", internalMemoryApp);
35036
35281
  app.route("/youtube", youtubeApp);
35037
35282
  app.route("/screenshot", screenshotApp);
35038
35283
  app.route("/facebook", facebookAdApp);
35039
35284
  app.route("/google-ads", googleAdsApp);
35040
35285
  app.route("/instagram", instagramApp);
35041
35286
  app.route("/reddit", redditApp);
35287
+ app.route("/video", videoApp);
35042
35288
  app.route("/maps", mapsApp);
35043
35289
  app.route("/directory", directoryApp);
35044
35290
  app.route("/workflows", workflowApp);