mcp-scraper 0.65.1 → 0.66.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +11 -6
  3. package/dist/{analytics-repository-IKE53S3E.js → analytics-repository-RUXBFSFX.js} +1 -2
  4. package/dist/bin/api-server.cjs +1844 -71
  5. package/dist/bin/api-server.js +4 -5
  6. package/dist/bin/mcp-scraper-cli.cjs +1 -2
  7. package/dist/bin/mcp-scraper-cli.js +6 -7
  8. package/dist/bin/mcp-scraper-install.cjs +7 -3
  9. package/dist/bin/mcp-scraper-install.js +2 -3
  10. package/dist/bin/mcp-stdio-server.cjs +5150 -3467
  11. package/dist/bin/mcp-stdio-server.js +11 -11
  12. package/dist/bin/paa-harvest.cjs +8 -3
  13. package/dist/bin/paa-harvest.js +4 -5
  14. package/dist/{chunk-5PIO7QBG.js → chunk-3PIXKLGZ.js} +3 -4
  15. package/dist/{chunk-VIWT35S6.js → chunk-4OT7BAIE.js} +1 -2
  16. package/dist/{chunk-ORKXE7VX.js → chunk-4P3PHFPQ.js} +11 -6
  17. package/dist/{chunk-AMLFKPLL.js → chunk-75VW7CWE.js} +4 -7
  18. package/dist/{chunk-FG6LJGJ7.js → chunk-77LQV73B.js} +0 -1
  19. package/dist/{chunk-DKIG2TEE.js → chunk-AQKJOSIB.js} +2 -3
  20. package/dist/{chunk-2XUXVKT4.js → chunk-AYJHI2FK.js} +0 -1
  21. package/dist/{chunk-5BEPOFKG.js → chunk-CNLLTD5B.js} +0 -1
  22. package/dist/{chunk-EKJ65E3Z.js → chunk-DNM65UCK.js} +0 -1
  23. package/dist/{chunk-PDAKCYHZ.js → chunk-E4LMY5OJ.js} +6258 -3747
  24. package/dist/{chunk-MQGBCWZD.js → chunk-GGZEC22A.js} +64 -1
  25. package/dist/{chunk-4VG3NUUY.js → chunk-GTGSD4YS.js} +2 -2
  26. package/dist/{chunk-MQQS7ONH.js → chunk-KBTUPRTF.js} +8 -296
  27. package/dist/{chunk-XPEN6OG5.js → chunk-NS754CRS.js} +15 -3
  28. package/dist/{chunk-UN4LZVCB.js → chunk-OZJMVCDK.js} +1 -2
  29. package/dist/{chunk-342OUCSG.js → chunk-PGJQDMC2.js} +1 -2
  30. package/dist/{chunk-G3P3ZDB4.js → chunk-RJ7JVYKU.js} +0 -1
  31. package/dist/{chunk-N54JS5WP.js → chunk-TNJIPV74.js} +1 -2
  32. package/dist/{chunk-4LINSJNI.js → chunk-TNLGHNLX.js} +1 -2
  33. package/dist/chunk-VRJ7CO6L.js +6 -0
  34. package/dist/chunk-VXLU74YZ.js +293 -0
  35. package/dist/{chunk-DCWXVAQT.js → chunk-WID3MFRS.js} +6 -2
  36. package/dist/{db-BRI465XQ.js → db-PO7N7ZP3.js} +1 -2
  37. package/dist/{extract-bundle-UWKJT4MU.js → extract-bundle-SIBFUA6I.js} +8 -10
  38. package/dist/index.cjs +8 -3
  39. package/dist/index.js +4 -5
  40. package/dist/{lead-list-enrichment-repository-ODWGRVCQ.js → lead-list-enrichment-repository-OTAUQONJ.js} +2 -3
  41. package/dist/{location-data-repository-67MMEVZQ.js → location-data-repository-RM2KJ3H5.js} +3 -4
  42. package/dist/{server-RSHSZBUZ.js → server-ELEJNRRE.js} +875 -1629
  43. package/dist/{site-extract-repository-J3RH3MOS.js → site-extract-repository-ASYEJO7Z.js} +4 -5
  44. package/dist/{worker-YKRIK5LS.js → worker-MAVYYWM7.js} +8 -9
  45. package/package.json +3 -1
  46. package/dist/analytics-repository-IKE53S3E.js.map +0 -1
  47. package/dist/bin/api-server.cjs.map +0 -1
  48. package/dist/bin/api-server.js.map +0 -1
  49. package/dist/bin/mcp-scraper-cli.cjs.map +0 -1
  50. package/dist/bin/mcp-scraper-cli.js.map +0 -1
  51. package/dist/bin/mcp-scraper-install.cjs.map +0 -1
  52. package/dist/bin/mcp-scraper-install.js.map +0 -1
  53. package/dist/bin/mcp-stdio-server.cjs.map +0 -1
  54. package/dist/bin/mcp-stdio-server.js.map +0 -1
  55. package/dist/bin/paa-harvest.cjs.map +0 -1
  56. package/dist/bin/paa-harvest.js.map +0 -1
  57. package/dist/chunk-2XUXVKT4.js.map +0 -1
  58. package/dist/chunk-342OUCSG.js.map +0 -1
  59. package/dist/chunk-4LINSJNI.js.map +0 -1
  60. package/dist/chunk-4VG3NUUY.js.map +0 -1
  61. package/dist/chunk-5BEPOFKG.js.map +0 -1
  62. package/dist/chunk-5PIO7QBG.js.map +0 -1
  63. package/dist/chunk-AMLFKPLL.js.map +0 -1
  64. package/dist/chunk-CGKWNXJR.js +0 -7
  65. package/dist/chunk-CGKWNXJR.js.map +0 -1
  66. package/dist/chunk-DCWXVAQT.js.map +0 -1
  67. package/dist/chunk-DKIG2TEE.js.map +0 -1
  68. package/dist/chunk-EKJ65E3Z.js.map +0 -1
  69. package/dist/chunk-FG6LJGJ7.js.map +0 -1
  70. package/dist/chunk-G3P3ZDB4.js.map +0 -1
  71. package/dist/chunk-MQGBCWZD.js.map +0 -1
  72. package/dist/chunk-MQQS7ONH.js.map +0 -1
  73. package/dist/chunk-N54JS5WP.js.map +0 -1
  74. package/dist/chunk-ORKXE7VX.js.map +0 -1
  75. package/dist/chunk-PDAKCYHZ.js.map +0 -1
  76. package/dist/chunk-UN4LZVCB.js.map +0 -1
  77. package/dist/chunk-UVFB24RZ.js +0 -67
  78. package/dist/chunk-UVFB24RZ.js.map +0 -1
  79. package/dist/chunk-VIWT35S6.js.map +0 -1
  80. package/dist/chunk-XPEN6OG5.js.map +0 -1
  81. package/dist/db-BRI465XQ.js.map +0 -1
  82. package/dist/extract-bundle-UWKJT4MU.js.map +0 -1
  83. package/dist/index.cjs.map +0 -1
  84. package/dist/index.js.map +0 -1
  85. package/dist/lead-list-enrichment-repository-ODWGRVCQ.js.map +0 -1
  86. package/dist/location-data-repository-67MMEVZQ.js.map +0 -1
  87. package/dist/server-RSHSZBUZ.js.map +0 -1
  88. package/dist/site-extract-repository-J3RH3MOS.js.map +0 -1
  89. package/dist/worker-YKRIK5LS.js.map +0 -1
@@ -46,7 +46,7 @@ var init_harvest_timeout = __esm({
46
46
  "src/harvest-timeout.ts"() {
47
47
  "use strict";
48
48
  SCRAPE_FUNCTION_MAX_MS = 8e5;
49
- DEFAULT_TOOL_CLIENT_TIMEOUT_MS = 3e5;
49
+ DEFAULT_TOOL_CLIENT_TIMEOUT_MS = 59e4;
50
50
  CLIENT_OVER_SERVER_MARGIN_MS = 15e3;
51
51
  }
52
52
  });
@@ -186,8 +186,9 @@ function publicErrorMessage(code) {
186
186
  case "proxy_unavailable":
187
187
  return "The web request could not establish a working connection. Please retry later.";
188
188
  case "harvest_timeout":
189
- case "mcp_request_timeout":
190
189
  return "The request timed out before it completed. Please retry with a smaller request.";
190
+ case "mcp_request_timeout":
191
+ return "The MCP caller stopped waiting before a final response arrived. Durable work may still be running; continue with the returned Task or job handle instead of restarting it.";
191
192
  case "concurrency_limit_exceeded":
192
193
  return "The service is available, but this account has reached its parallel-operation limit. Wait for an active operation to finish or add capacity.";
193
194
  case "unauthorized":
@@ -16552,7 +16553,7 @@ var init_site_extract_repository = __esm({
16552
16553
 
16553
16554
  // src/api/vendor-errors.ts
16554
16555
  function isVendorUnavailableError(err, message) {
16555
- return err instanceof import_sdk5.AuthenticationError || err instanceof import_sdk5.PermissionDeniedError || err instanceof import_sdk5.APIConnectionError || err instanceof import_sdk5.APIConnectionTimeoutError || err instanceof import_sdk5.InternalServerError || message === BRIGHTDATA_NOT_CONFIGURED_MESSAGE;
16556
+ return err instanceof import_sdk5.AuthenticationError || err instanceof import_sdk5.PermissionDeniedError || err instanceof import_sdk5.APIConnectionError || err instanceof import_sdk5.APIConnectionTimeoutError || err instanceof import_sdk5.InternalServerError || /^401\s+Invalid or disabled API key\s*$/i.test(message.trim()) || message === BRIGHTDATA_NOT_CONFIGURED_MESSAGE;
16556
16557
  }
16557
16558
  var import_sdk5, BRIGHTDATA_NOT_CONFIGURED_MESSAGE;
16558
16559
  var init_vendor_errors = __esm({
@@ -27069,6 +27070,19 @@ async function acquireConcurrencyGate(user, operation, options = {}) {
27069
27070
  if (Number(res.rowsAffected ?? 0) > 0) {
27070
27071
  return { ok: true, lockId, active, limit, operation };
27071
27072
  }
27073
+ await logRequestEvent({
27074
+ userId: user.id,
27075
+ source: "concurrency_gate",
27076
+ status: "failed",
27077
+ query: operation,
27078
+ result: { active, limit },
27079
+ error: "concurrency_limit_exceeded"
27080
+ }).catch((error) => {
27081
+ console.error("[concurrency-gate] rejection telemetry failed", {
27082
+ operation,
27083
+ error: error instanceof Error ? error.message : String(error)
27084
+ });
27085
+ });
27072
27086
  return { ok: false, active, limit, operation, retryAfterSeconds: DEFAULT_RETRY_AFTER_SECONDS };
27073
27087
  }
27074
27088
  async function releaseConcurrencyGate(lockId) {
@@ -29195,6 +29209,9 @@ function classifyAttemptError(err) {
29195
29209
  if (err instanceof RequestAbortedError) return "request_aborted";
29196
29210
  if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
29197
29211
  const message = err instanceof Error ? err.message : String(err);
29212
+ if (/browser (?:has been )?closed|context (?:has been )?closed|target page, context or browser has been closed|session closed|page has been closed/i.test(message)) {
29213
+ return "browser_session_interrupted";
29214
+ }
29198
29215
  if (looksLikeProxyTunnelFailure2(message)) return "proxy_tunnel_failed";
29199
29216
  if (looksLikeProxyUnavailable2(message)) return "proxy_unavailable";
29200
29217
  return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
@@ -29242,6 +29259,9 @@ function looksLikeProxyUnavailable2(message) {
29242
29259
  function retryableLocationProxyError(outcome) {
29243
29260
  return outcome === "captcha" || outcome === "proxy_tunnel_failed" || outcome === "proxy_unavailable";
29244
29261
  }
29262
+ function retryableFreshBrowserError(outcome) {
29263
+ return outcome === "browser_session_interrupted";
29264
+ }
29245
29265
  function locationMismatchMessage(result) {
29246
29266
  const evidence = result.diagnostics.debug?.locationEvidence;
29247
29267
  const expected = evidence?.expected?.canonicalLocation ?? result.location ?? "requested location";
@@ -29425,7 +29445,7 @@ async function harvest(rawOptions) {
29425
29445
  proxyFailures++;
29426
29446
  if (proxyFailures >= 2) effectiveProxyMode = "none";
29427
29447
  }
29428
- const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
29448
+ const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || retryableFreshBrowserError(outcome2) || proxyMode === "location" && retryableLocationProxyError(outcome2));
29429
29449
  if (outcome2 === "captcha") {
29430
29450
  console.warn(JSON.stringify({
29431
29451
  event: "harvest_attempt_captcha",
@@ -29543,7 +29563,7 @@ async function harvest(rawOptions) {
29543
29563
  proxyFailures++;
29544
29564
  if (proxyFailures >= 2) effectiveProxyMode = "none";
29545
29565
  }
29546
- const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
29566
+ const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || retryableFreshBrowserError(outcome) || proxyMode === "location" && retryableLocationProxyError(outcome));
29547
29567
  if (outcome === "captcha") {
29548
29568
  console.warn(JSON.stringify({
29549
29569
  event: "harvest_attempt_captcha",
@@ -29781,7 +29801,7 @@ function businessWords(value) {
29781
29801
  return tokens2;
29782
29802
  }
29783
29803
  function cleanName(value) {
29784
- return value.replace(/^(?:Mr\.?|Ms\.?|Mrs\.?|Dr\.?)\s+/i, "").replace(/\s+/g, " ").trim();
29804
+ return value.replace(/^(?:Mr\.?|Ms\.?|Mrs\.?|Dr\.?)\s+/i, "").replace(/[.!?]\s+.*$/, "").replace(/[.,;:!?]+$/, "").replace(/\s+/g, " ").trim();
29785
29805
  }
29786
29806
  function plausibleName(value, businessName) {
29787
29807
  const tokens2 = words(value);
@@ -29798,6 +29818,8 @@ function relationshipFor(role) {
29798
29818
  return "team_member";
29799
29819
  }
29800
29820
  function normalizedRole(role) {
29821
+ if (/founded/i.test(role)) return "Founder";
29822
+ if (/owned/i.test(role)) return "Owner";
29801
29823
  return compact2(role, 80).replace(/\bceo\b/i, "CEO").replace(/\b\w/g, (letter) => letter.toUpperCase());
29802
29824
  }
29803
29825
  function resultContext(result) {
@@ -29810,24 +29832,80 @@ function orderedResults(results) {
29810
29832
  return a - b || left.index - right.index;
29811
29833
  }).map((item) => item.result);
29812
29834
  }
29835
+ function publicHttpUrl(value) {
29836
+ if (!value) return null;
29837
+ try {
29838
+ const parsed = new URL(value);
29839
+ return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : null;
29840
+ } catch {
29841
+ return null;
29842
+ }
29843
+ }
29813
29844
  function quoted(value) {
29814
29845
  return `"${value.replace(/["\\]/g, " ").replace(/\s+/g, " ").trim()}"`;
29815
29846
  }
29816
- function buildAssociatedPeopleQuery(input) {
29817
- const roles = input.mode === "owners" ? 'owner founder "managing member" president' : "owner founder president manager leadership team";
29818
- return [quoted(input.businessName), roles, compact2(input.location ?? "", 120)].filter(Boolean).join(" ");
29847
+ function queryValue(value) {
29848
+ return compact2(value ?? "", 120).replace(/["\\]/g, " ");
29849
+ }
29850
+ function defaultQueryTemplate(mode) {
29851
+ return mode === "owners" ? '"{business}" owner of company {location}' : '"{business}" leadership team {location}';
29852
+ }
29853
+ function buildAssociatedPeopleQueries(input) {
29854
+ const values = {
29855
+ business: queryValue(input.businessName),
29856
+ city: queryValue(input.city),
29857
+ region: queryValue(input.region),
29858
+ location: queryValue(input.location)
29859
+ };
29860
+ const queries = (input.queryTemplates?.length ? input.queryTemplates : [defaultQueryTemplate(input.mode)]).map((template) => compact2(template.replace(/\{(business|city|region|location)\}/g, (_match, key) => values[key]), 240)).filter(Boolean);
29861
+ return [...new Set(queries)].slice(0, 3);
29819
29862
  }
29820
29863
  function buildLinkedInPersonQuery(input) {
29821
29864
  return `site:linkedin.com/in ${quoted(input.personName)} ${quoted(input.businessName)}`;
29822
29865
  }
29823
29866
  function extractAssociatedPeopleEvidence(input) {
29824
29867
  const accepted = /* @__PURE__ */ new Map();
29868
+ const documents = [];
29869
+ const overviewText = input.aiOverview?.detected ? compact2(input.aiOverview.text ?? "") : "";
29870
+ if (overviewText) {
29871
+ const searchUrl = publicHttpUrl(input.aiOverview?.shareUrl) ?? (input.query ? `https://www.google.com/search?q=${encodeURIComponent(input.query)}` : "https://www.google.com/");
29872
+ const citationUrls = (input.aiOverview?.citations ?? []).flatMap((citation) => {
29873
+ try {
29874
+ const parsed = new URL(citation.href);
29875
+ return ["http:", "https:"].includes(parsed.protocol) ? [parsed.href] : [];
29876
+ } catch {
29877
+ return [];
29878
+ }
29879
+ }).slice(0, 5);
29880
+ documents.push({
29881
+ sourceType: "ai_overview",
29882
+ sourceUrl: searchUrl,
29883
+ sourceTitle: "Google AI Overview",
29884
+ sourcePosition: 0,
29885
+ context: overviewText,
29886
+ ...citationUrls.length ? { citationUrls } : {}
29887
+ });
29888
+ }
29825
29889
  for (const result of orderedResults(input.results)) {
29826
- const context = resultContext(result);
29890
+ documents.push({
29891
+ sourceType: "serp_snippet",
29892
+ sourceUrl: result.url,
29893
+ sourceTitle: result.title,
29894
+ sourcePosition: result.position,
29895
+ context: resultContext(result)
29896
+ });
29897
+ }
29898
+ for (const document2 of documents) {
29899
+ const context = document2.context;
29827
29900
  if (!containsBusiness(context, input.businessName)) continue;
29901
+ const disambiguatingPlace = input.city || (input.location ?? "").split(",")[0] || "";
29902
+ const locationTokens = words(disambiguatingPlace).filter((token6) => token6.length > 2);
29903
+ const locationMatched = locationTokens.some((token6) => words(context).includes(token6));
29904
+ if (document2.sourceType === "ai_overview" && locationTokens.length && !locationMatched) continue;
29828
29905
  const matches = [];
29829
29906
  for (const match of context.matchAll(new RegExp(NAME_THEN_ROLE_SOURCE, "giu"))) matches.push({ name: match[1] ?? "", role: match[2] ?? "" });
29830
29907
  for (const match of context.matchAll(new RegExp(ROLE_THEN_NAME_SOURCE, "giu"))) matches.push({ name: match[2] ?? "", role: match[1] ?? "" });
29908
+ for (const match of context.matchAll(new RegExp(PASSIVE_ROLE_THEN_NAME_SOURCE, "giu"))) matches.push({ name: match[2] ?? "", role: match[1] ?? "" });
29831
29909
  for (const match of matches) {
29832
29910
  const name = cleanName(match.name);
29833
29911
  const role = normalizedRole(match.role);
@@ -29835,22 +29913,36 @@ function extractAssociatedPeopleEvidence(input) {
29835
29913
  const relationship = relationshipFor(role);
29836
29914
  if (input.mode === "owners" && relationship !== "owner" && relationship !== "founder") continue;
29837
29915
  const key = words(name).join(" ");
29838
- if (!key || accepted.has(key)) continue;
29916
+ if (!key) continue;
29917
+ const source = {
29918
+ sourceType: document2.sourceType,
29919
+ purpose: "role",
29920
+ sourceUrl: document2.sourceUrl,
29921
+ sourceTitle: document2.sourceTitle,
29922
+ sourcePosition: document2.sourcePosition,
29923
+ context,
29924
+ ...document2.citationUrls ? { citationUrls: document2.citationUrls } : {}
29925
+ };
29926
+ const existing = accepted.get(key);
29927
+ if (existing) {
29928
+ if (existing.sources.length < 2 && !existing.sources.some((item) => item.sourceUrl === source.sourceUrl)) existing.sources.push(source);
29929
+ existing.reasons = [.../* @__PURE__ */ new Set([...existing.reasons, "multiple_search_sources"])];
29930
+ existing.confidence = Math.max(existing.confidence, document2.sourceType === "ai_overview" ? locationMatched ? 0.93 : 0.9 : 0.88);
29931
+ continue;
29932
+ }
29933
+ const aiOverview = document2.sourceType === "ai_overview";
29839
29934
  accepted.set(key, {
29840
29935
  name,
29841
29936
  role,
29842
29937
  relationship,
29843
29938
  linkedinUrl: null,
29844
- confidence: 0.88,
29845
- reasons: ["name_role_in_search_result", "exact_business_query_context"],
29846
- sources: [{
29847
- sourceType: "serp_snippet",
29848
- purpose: "role",
29849
- sourceUrl: result.url,
29850
- sourceTitle: result.title,
29851
- sourcePosition: result.position,
29852
- context
29853
- }]
29939
+ confidence: aiOverview ? locationMatched ? 0.93 : 0.9 : 0.88,
29940
+ reasons: [
29941
+ aiOverview ? "name_role_in_ai_overview" : "name_role_in_search_result",
29942
+ "exact_business_query_context",
29943
+ ...aiOverview && locationMatched ? ["ai_overview_location_match"] : []
29944
+ ],
29945
+ sources: [source]
29854
29946
  });
29855
29947
  if (accepted.size >= MAX_CANDIDATES) return [...accepted.values()];
29856
29948
  }
@@ -29906,7 +29998,7 @@ function attachLinkedInProfile(input) {
29906
29998
  }
29907
29999
  return input.person;
29908
30000
  }
29909
- var MAX_CONTEXT_CHARS3, MAX_CANDIDATES, ROLE, NAME, NAME_THEN_ROLE_SOURCE, ROLE_THEN_NAME_SOURCE, NAME_STOP_WORDS, LEGAL_SUFFIXES;
30001
+ var MAX_CONTEXT_CHARS3, MAX_CANDIDATES, ROLE, NAME, NAME_THEN_ROLE_SOURCE, ROLE_THEN_NAME_SOURCE, PASSIVE_ROLE_THEN_NAME_SOURCE, NAME_STOP_WORDS, LEGAL_SUFFIXES;
29910
30002
  var init_serp_people_evidence = __esm({
29911
30003
  "src/leads/serp-people-evidence.ts"() {
29912
30004
  "use strict";
@@ -29934,7 +30026,8 @@ var init_serp_people_evidence = __esm({
29934
30026
  ].join("|");
29935
30027
  NAME = String.raw`(?:Mr\.?|Ms\.?|Mrs\.?|Dr\.?)?\s*([\p{Lu}][\p{L}'’.-]+(?:\s+[\p{Lu}][\p{L}'’.-]+){1,3})`;
29936
30028
  NAME_THEN_ROLE_SOURCE = `${NAME}\\s*(?:,|[-\u2013\u2014]|\\bis\\b|\\bas\\b|\\bserves as\\b)?\\s*(${ROLE})\\b`;
29937
- ROLE_THEN_NAME_SOURCE = `\\b(${ROLE})\\b\\s*(?:[,:\u2013\u2014-]|\\bis\\b|\\bof\\b)\\s*${NAME}`;
30029
+ ROLE_THEN_NAME_SOURCE = `\\b(${ROLE})\\b\\s*(?:[,:\u2013\u2014-]|\\bis\\b|\\bof\\b)?\\s*${NAME}`;
30030
+ PASSIVE_ROLE_THEN_NAME_SOURCE = `\\b((?:co[- ]?)?owned|founded)\\s+by\\s+${NAME}`;
29938
30031
  NAME_STOP_WORDS = /* @__PURE__ */ new Set([
29939
30032
  "about",
29940
30033
  "additional",
@@ -29971,7 +30064,7 @@ function privateIpv4(hostname) {
29971
30064
  if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
29972
30065
  return parts[0] === 10 || parts[0] === 127 || parts[0] === 169 && parts[1] === 254 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168 || parts[0] === 0;
29973
30066
  }
29974
- function publicHttpUrl(value) {
30067
+ function publicHttpUrl2(value) {
29975
30068
  if (!value) return null;
29976
30069
  try {
29977
30070
  const parsed = new URL(value);
@@ -30039,8 +30132,8 @@ function normalizeLeadRows(input) {
30039
30132
  if (!name) throw new Error(`Row ${ordinal + 1} requires a non-empty name`);
30040
30133
  const websiteRaw = mappedValue(source, columnMap.websiteUrl);
30041
30134
  const profileRaw = mappedValue(source, columnMap.profileUrl);
30042
- const websiteUrl = websiteRaw ? publicHttpUrl(websiteRaw) : null;
30043
- const profileUrl = profileRaw ? publicHttpUrl(profileRaw) : null;
30135
+ const websiteUrl = websiteRaw ? publicHttpUrl2(websiteRaw) : null;
30136
+ const profileUrl = profileRaw ? publicHttpUrl2(profileRaw) : null;
30044
30137
  if (websiteRaw && !websiteUrl || profileRaw && !profileUrl) throw new Error(`Row ${ordinal + 1} URL must be a public HTTP(S) URL`);
30045
30138
  const location2 = mappedValue(source, columnMap.location);
30046
30139
  const city = mappedValue(source, columnMap.city);
@@ -30147,6 +30240,9 @@ function rankIdentityCandidates(row, candidates) {
30147
30240
  function serpResults(response) {
30148
30241
  return Array.isArray(response) ? response : response.organicResults;
30149
30242
  }
30243
+ function peopleSerpEvidence(response) {
30244
+ return Array.isArray(response) ? { organicResults: response } : response;
30245
+ }
30150
30246
  async function discoverAssociatedPeople(input) {
30151
30247
  if (input.options.peopleDiscovery === "off") return [];
30152
30248
  if (input.mapped.entityType !== "business") {
@@ -30160,23 +30256,42 @@ async function discoverAssociatedPeople(input) {
30160
30256
  const searchPeopleSerp = input.adapters.searchPeopleSerp;
30161
30257
  const mode = input.options.peopleDiscovery;
30162
30258
  const location2 = combineLocation(input.mapped.location, input.mapped.city, input.mapped.region);
30163
- let candidates;
30164
- try {
30165
- const response = await searchPeopleSerp({
30166
- query: buildAssociatedPeopleQuery({ businessName: input.mapped.name, location: location2, mode }),
30167
- gl: input.options.gl,
30168
- hl: input.options.hl
30169
- });
30170
- input.usage.serpSearches += 1;
30171
- candidates = extractAssociatedPeopleEvidence({
30172
- businessName: input.mapped.name,
30173
- mode,
30174
- results: serpResults(response)
30175
- }).slice(0, input.options.maxPeoplePerLead);
30176
- } catch {
30177
- input.warnings.push("people_search_failed");
30178
- return [];
30259
+ const queries = buildAssociatedPeopleQueries({
30260
+ businessName: input.mapped.name,
30261
+ location: location2,
30262
+ city: input.mapped.city,
30263
+ region: input.mapped.region,
30264
+ mode,
30265
+ queryTemplates: input.options.peopleQueryTemplates
30266
+ });
30267
+ const candidatesByName = /* @__PURE__ */ new Map();
30268
+ let completedRoleSearches = 0;
30269
+ for (const query of queries) {
30270
+ try {
30271
+ const response = await searchPeopleSerp({ query, gl: input.options.gl, hl: input.options.hl, includeAiOverview: true });
30272
+ input.usage.serpSearches += 1;
30273
+ completedRoleSearches += 1;
30274
+ const evidence = peopleSerpEvidence(response);
30275
+ for (const person of extractAssociatedPeopleEvidence({
30276
+ businessName: input.mapped.name,
30277
+ location: location2,
30278
+ city: input.mapped.city,
30279
+ query,
30280
+ mode,
30281
+ results: evidence.organicResults,
30282
+ aiOverview: evidence.aiOverview
30283
+ })) {
30284
+ const key = person.name.toLocaleLowerCase("en-US");
30285
+ if (!candidatesByName.has(key)) candidatesByName.set(key, person);
30286
+ if (candidatesByName.size >= input.options.maxPeoplePerLead) break;
30287
+ }
30288
+ if (candidatesByName.size >= input.options.maxPeoplePerLead) break;
30289
+ } catch {
30290
+ input.warnings.push("people_search_failed");
30291
+ }
30179
30292
  }
30293
+ const candidates = [...candidatesByName.values()].slice(0, input.options.maxPeoplePerLead);
30294
+ if (!completedRoleSearches) return [];
30180
30295
  if (!candidates.length) {
30181
30296
  input.warnings.push("people_not_found");
30182
30297
  return [];
@@ -30187,7 +30302,8 @@ async function discoverAssociatedPeople(input) {
30187
30302
  const response = await searchPeopleSerp({
30188
30303
  query: buildLinkedInPersonQuery({ personName: person.name, businessName: input.mapped.name }),
30189
30304
  gl: input.options.gl,
30190
- hl: input.options.hl
30305
+ hl: input.options.hl,
30306
+ includeAiOverview: false
30191
30307
  });
30192
30308
  input.usage.serpSearches += 1;
30193
30309
  people.push(attachLinkedInProfile({
@@ -30491,7 +30607,7 @@ function leadListOutputRows(results, sourceHeaders) {
30491
30607
  })
30492
30608
  };
30493
30609
  }
30494
- var import_zod13, LeadScalarSchema, RESERVED_OUTPUT_PREFIX, MAX_ROWS, MAX_COLUMNS, MAX_CELL_BYTES, MAX_INLINE_BYTES, MAX_NORMALIZED_ARTIFACT_BYTES, LeadListRowsSchema, LeadColumnMapSchema, LeadListEnrichmentOptionsSchema, DEFAULT_HEADER_ALIASES, LEGAL_SUFFIXES2, LEAD_LIST_ENRICHED_HEADERS;
30610
+ var import_zod13, LeadScalarSchema, RESERVED_OUTPUT_PREFIX, MAX_ROWS, MAX_COLUMNS, MAX_CELL_BYTES, MAX_INLINE_BYTES, MAX_NORMALIZED_ARTIFACT_BYTES, LeadListRowsSchema, LeadColumnMapSchema, PEOPLE_QUERY_TEMPLATE_PLACEHOLDER, LeadPeopleQueryTemplateSchema, LeadPeopleQueryTemplatesSchema, LeadListEnrichmentOptionsSchema, DEFAULT_HEADER_ALIASES, LEGAL_SUFFIXES2, LEAD_LIST_ENRICHED_HEADERS;
30495
30611
  var init_lead_list_enrichment = __esm({
30496
30612
  "src/leads/lead-list-enrichment.ts"() {
30497
30613
  "use strict";
@@ -30527,6 +30643,9 @@ var init_lead_list_enrichment = __esm({
30527
30643
  email: import_zod13.z.string().min(1).optional(),
30528
30644
  entityType: import_zod13.z.string().min(1).optional()
30529
30645
  }).strict();
30646
+ PEOPLE_QUERY_TEMPLATE_PLACEHOLDER = /\{(?:business|city|region|location)\}/g;
30647
+ LeadPeopleQueryTemplateSchema = import_zod13.z.string().trim().min(1).max(240).refine((value) => !/[\r\n]/.test(value), "People query templates must be one line").refine((value) => value.includes("{business}"), "People query templates must include {business}").refine((value) => !/[{}]/.test(value.replace(PEOPLE_QUERY_TEMPLATE_PLACEHOLDER, "")), "People query templates may use only {business}, {city}, {region}, and {location}");
30648
+ LeadPeopleQueryTemplatesSchema = import_zod13.z.array(LeadPeopleQueryTemplateSchema).min(1).max(3);
30530
30649
  LeadListEnrichmentOptionsSchema = import_zod13.z.object({
30531
30650
  resolveMissingUrls: import_zod13.z.boolean().default(true),
30532
30651
  defaultEntityType: import_zod13.z.enum(["business", "person"]).default("business"),
@@ -30536,6 +30655,7 @@ var init_lead_list_enrichment = __esm({
30536
30655
  concurrency: import_zod13.z.number().int().min(1).max(5).default(3),
30537
30656
  emailSearchFallback: import_zod13.z.enum(["off", "serp_snippets"]).default("off"),
30538
30657
  peopleDiscovery: import_zod13.z.enum(["off", "owners", "leadership"]).default("off"),
30658
+ peopleQueryTemplates: LeadPeopleQueryTemplatesSchema.optional(),
30539
30659
  maxPeoplePerLead: import_zod13.z.number().int().min(1).max(3).default(2),
30540
30660
  outputFormats: import_zod13.z.array(import_zod13.z.enum(["csv", "xlsx"])).min(1).max(2).transform((values) => [...new Set(values)]).default(["csv", "xlsx"])
30541
30661
  }).strict();
@@ -31468,12 +31588,13 @@ function createLeadListEnrichmentAdapters(userId) {
31468
31588
  pages: 1,
31469
31589
  maxQuestions: 1,
31470
31590
  headless: true,
31591
+ includeAiOverview: input.includeAiOverview === true,
31471
31592
  format: "json",
31472
31593
  outputDir: "/tmp/paa-output-api",
31473
31594
  kernelApiKey,
31474
31595
  proxyMode: DEFAULT_PROXY_MODE
31475
31596
  }));
31476
- return result.organicResults;
31597
+ return result;
31477
31598
  };
31478
31599
  return {
31479
31600
  async mapsSearch(input) {
@@ -31506,8 +31627,13 @@ function createLeadListEnrichmentAdapters(userId) {
31506
31627
  contactEvidence: page.contactEvidence
31507
31628
  };
31508
31629
  },
31509
- searchEmailSerp: searchSerp,
31510
- searchPeopleSerp: searchSerp
31630
+ async searchEmailSerp(input) {
31631
+ return (await searchSerp(input)).organicResults;
31632
+ },
31633
+ async searchPeopleSerp(input) {
31634
+ const result = await searchSerp(input);
31635
+ return { organicResults: result.organicResults, aiOverview: result.aiOverview };
31636
+ }
31511
31637
  };
31512
31638
  }
31513
31639
  function cancelledResult(row) {
@@ -31906,6 +32032,9 @@ function looksLikeProxyUnavailable3(message) {
31906
32032
  function looksLikeInternalServiceUnavailable(message) {
31907
32033
  return /(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\/\/\S*dashboard/i.test(message);
31908
32034
  }
32035
+ function looksLikeBrowserSessionInterrupted(message) {
32036
+ return /browser (?:has been )?closed|context (?:has been )?closed|target page, context or browser has been closed|session closed|page has been closed/i.test(message);
32037
+ }
31909
32038
  function classifyHarvestProblem(err) {
31910
32039
  const message = errorMessage4(err);
31911
32040
  if (err instanceof RequestAbortedError) {
@@ -31968,6 +32097,16 @@ function classifyHarvestProblem(err) {
31968
32097
  terminalStatus: "failed"
31969
32098
  };
31970
32099
  }
32100
+ if (looksLikeBrowserSessionInterrupted(message)) {
32101
+ return {
32102
+ error_code: "browser_session_interrupted",
32103
+ error_type: "extraction",
32104
+ message: publicErrorMessage("browser_session_interrupted"),
32105
+ retryable: true,
32106
+ httpStatus: 503,
32107
+ terminalStatus: "failed"
32108
+ };
32109
+ }
31971
32110
  if (looksLikeTimeout(err, message)) {
31972
32111
  return {
31973
32112
  error_code: "harvest_timeout",
@@ -45731,7 +45870,7 @@ var init_lead_list_enrichment_routes = __esm({
45731
45870
  if (!gate.ok) {
45732
45871
  return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
45733
45872
  }
45734
- const holdMc = input.rows.length * ((input.options.resolveMissingUrls ? MC_COSTS.maps_search : 0) + input.options.maxPagesPerLead * MC_COSTS.page_scrape + (input.options.emailSearchFallback === "serp_snippets" ? MC_COSTS.serp_headful : 0) + (input.options.peopleDiscovery === "off" ? 0 : (1 + input.options.maxPeoplePerLead) * MC_COSTS.serp_headful));
45873
+ const holdMc = input.rows.length * ((input.options.resolveMissingUrls ? MC_COSTS.maps_search : 0) + input.options.maxPagesPerLead * MC_COSTS.page_scrape + (input.options.emailSearchFallback === "serp_snippets" ? MC_COSTS.serp_headful : 0) + (input.options.peopleDiscovery === "off" ? 0 : ((input.options.peopleQueryTemplates?.length ?? 1) + input.options.maxPeoplePerLead) * MC_COSTS.serp_headful));
45735
45874
  let creation;
45736
45875
  try {
45737
45876
  creation = await createOrGetLeadListEnrichmentJob({
@@ -49709,6 +49848,7 @@ var init_schemas4 = __esm({
49709
49848
  "location_mismatch",
49710
49849
  "proxy_tunnel_failed",
49711
49850
  "proxy_unavailable",
49851
+ "browser_session_interrupted",
49712
49852
  "mcp_unavailable",
49713
49853
  "error"
49714
49854
  ];
@@ -50397,7 +50537,7 @@ var PACKAGE_VERSION;
50397
50537
  var init_version = __esm({
50398
50538
  "src/version.ts"() {
50399
50539
  "use strict";
50400
- PACKAGE_VERSION = "0.65.1";
50540
+ PACKAGE_VERSION = "0.66.1";
50401
50541
  }
50402
50542
  });
50403
50543
 
@@ -50570,7 +50710,7 @@ decide whether the site needs a login:**
50570
50710
 
50571
50711
  ## Workflows
50572
50712
  Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when the whole job is one of these:
50573
- - Lead-list file bytes are unavailable to this MCP -> **lead_list_upload_start**. For CSV/TSV text or sheet/header inspection -> **lead_list_import**. For supplied mapped rows or an imported \`leadListId\` -> **lead_list_enrich**, then **lead_list_enrich_status**. Use its opt-in peopleDiscovery mode for owners, leadership, and company-matched LinkedIn profile candidates; do not manually open LinkedIn pages.
50713
+ - Lead-list file bytes are unavailable to this MCP -> **lead_list_upload_start**. For CSV/TSV text or sheet/header inspection -> **lead_list_import**. For supplied mapped rows or an imported \`leadListId\` -> **lead_list_enrich**, then **lead_list_enrich_status**. For owners or leadership, enable peopleDiscovery and author focused peopleQueryTemplates with one role intent per query; the tool reads AI Overview and organic evidence before company-matched LinkedIn candidates, so do not manually open LinkedIn pages.
50574
50714
  - Start without a supplied list, from a niche plus one market -> **workflow_run** with workflowId \`get-leads\`. For candidate-only Google Maps discovery -> **maps_search**.
50575
50715
  - Build a local business directory for a niche across a state/region -> **directory_workflow**.
50576
50716
  - Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
@@ -52246,6 +52386,7 @@ var init_mcp_tool_schemas = __esm({
52246
52386
  "src/mcp/mcp-tool-schemas.ts"() {
52247
52387
  "use strict";
52248
52388
  import_zod45 = require("zod");
52389
+ init_lead_list_enrichment();
52249
52390
  init_contracts();
52250
52391
  init_wayback_schemas();
52251
52392
  init_local_sourcebook_schema();
@@ -52605,8 +52746,9 @@ var init_mcp_tool_schemas = __esm({
52605
52746
  hl: import_zod45.z.string().trim().length(2).default("en").describe("Two-letter Google language code used only for missing-URL Maps resolution."),
52606
52747
  maxPagesPerLead: import_zod45.z.number().int().min(1).max(4).default(3).describe("Maximum public page extraction attempts per row, including its starting page."),
52607
52748
  emailSearchFallback: import_zod45.z.enum(["off", "serp_snippets"]).default("off").describe("Optional recovery after bounded page extraction finds no email. serp_snippets spends at most one current-rate SERP search per unresolved row and accepts only exact-domain email occurrences from result titles/snippets. These public occurrences do not verify deliverability or consent."),
52608
- peopleDiscovery: import_zod45.z.enum(["off", "owners", "leadership"]).default("off").describe("Optional associated-person recovery for business rows. owners finds explicit owners/founders; leadership also accepts role-bearing executives, managers, and team members. It performs one exact-business role search plus at most maxPeoplePerLead LinkedIn result searches, never opens LinkedIn pages, and returns provenance-bearing public candidates rather than verified employment."),
52609
- maxPeoplePerLead: import_zod45.z.number().int().min(1).max(3).default(2).describe("Maximum associated people retained and LinkedIn-searched per business row when peopleDiscovery is enabled. The maximum added SERP cost is one role search plus this many profile searches."),
52749
+ peopleDiscovery: import_zod45.z.enum(["off", "owners", "leadership"]).default("off").describe("Optional associated-person recovery for business rows. owners finds explicit owners/founders; leadership also accepts role-bearing executives, managers, and team members. It searches AI Overview plus organic results, never opens LinkedIn pages, and returns provenance-bearing public candidates rather than verified employment."),
52750
+ peopleQueryTemplates: LeadPeopleQueryTemplatesSchema.optional().describe("Ordered query templates the calling model should author when peopleDiscovery is enabled: use one role intent per template and the placeholders {business}, {city}, {region}, or {location}, for example {business} owner of company; one focused server fallback is used when omitted."),
52751
+ maxPeoplePerLead: import_zod45.z.number().int().min(1).max(3).default(2).describe("Maximum associated people retained and LinkedIn-searched per business row when peopleDiscovery is enabled; maximum added SERP cost is peopleQueryTemplates length (or one fallback) plus one profile search per retained person."),
52610
52752
  concurrency: import_zod45.z.number().int().min(1).max(5).default(3).describe("Maximum rows enriched concurrently. Default 3; maximum 5."),
52611
52753
  outputFormats: import_zod45.z.array(import_zod45.z.enum(["csv", "xlsx"])).min(1).max(2).default(["csv", "xlsx"]).refine((values) => new Set(values).size === values.length, "outputFormats must be unique").describe("Unique requested result formats. Defaults to both CSV and XLSX; this changes rendering only.")
52612
52754
  };
@@ -53292,12 +53434,13 @@ var init_mcp_tool_schemas = __esm({
53292
53434
  serpMc: import_zod45.z.number().int().min(0).optional()
53293
53435
  });
53294
53436
  LeadAssociatedPersonSourceOutputSchema = import_zod45.z.object({
53295
- sourceType: import_zod45.z.literal("serp_snippet"),
53437
+ sourceType: import_zod45.z.enum(["serp_snippet", "ai_overview"]),
53296
53438
  purpose: import_zod45.z.enum(["role", "linkedin"]),
53297
53439
  sourceUrl: import_zod45.z.string().url(),
53298
53440
  sourceTitle: import_zod45.z.string(),
53299
- sourcePosition: import_zod45.z.number().int().min(1),
53300
- context: import_zod45.z.string()
53441
+ sourcePosition: import_zod45.z.number().int().min(0),
53442
+ context: import_zod45.z.string(),
53443
+ citationUrls: import_zod45.z.array(import_zod45.z.string().url()).max(5).optional()
53301
53444
  }).strict();
53302
53445
  LeadAssociatedPersonOutputSchema = import_zod45.z.object({
53303
53446
  name: import_zod45.z.string(),
@@ -53306,7 +53449,7 @@ var init_mcp_tool_schemas = __esm({
53306
53449
  linkedinUrl: NullableString,
53307
53450
  confidence: import_zod45.z.number().min(0).max(1),
53308
53451
  reasons: import_zod45.z.array(import_zod45.z.string()).max(10),
53309
- sources: import_zod45.z.array(LeadAssociatedPersonSourceOutputSchema).max(2)
53452
+ sources: import_zod45.z.array(LeadAssociatedPersonSourceOutputSchema).max(3)
53310
53453
  }).strict();
53311
53454
  LeadSampleRowOutputSchema = import_zod45.z.object({
53312
53455
  people: import_zod45.z.array(LeadAssociatedPersonOutputSchema).max(3).optional()
@@ -55575,7 +55718,7 @@ function decodeTaskHandle(taskId, secret2, ownerId2) {
55575
55718
  decipher.update(ciphertext),
55576
55719
  decipher.final()
55577
55720
  ]).toString("utf8"));
55578
- if (parsed.v !== 1 || parsed.ownerId !== ownerId2 || !["site_export", "directory_workflow", "video_frame_analysis", "lead_list_enrichment"].includes(String(parsed.kind)) || typeof parsed.operationId !== "string" || !parsed.operationId || typeof parsed.createdAtMs !== "number" || typeof parsed.expiresAtMs !== "number") throw new Error("invalid payload");
55721
+ if (parsed.v !== 1 || parsed.ownerId !== ownerId2 || !["paa_harvest", "site_export", "directory_workflow", "video_frame_analysis", "lead_list_enrichment"].includes(String(parsed.kind)) || typeof parsed.operationId !== "string" || !parsed.operationId || typeof parsed.createdAtMs !== "number" || typeof parsed.expiresAtMs !== "number") throw new Error("invalid payload");
55579
55722
  if (Date.now() > parsed.expiresAtMs) {
55580
55723
  throw new import_server.ProtocolError(TASK_INVALID_PARAMS, "Failed to retrieve task: Task has expired");
55581
55724
  }
@@ -55588,9 +55731,25 @@ function decodeTaskHandle(taskId, secret2, ownerId2) {
55588
55731
  function objectValue(value) {
55589
55732
  return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
55590
55733
  }
55591
- function taskSeed(kind, result) {
55734
+ function resultValue(result) {
55592
55735
  const structured = objectValue(result.structuredContent);
55736
+ if (structured) return structured;
55737
+ const block = result.content.find((item) => item.type === "text");
55738
+ if (!block || block.type !== "text") return void 0;
55739
+ try {
55740
+ return objectValue(JSON.parse(block.text));
55741
+ } catch {
55742
+ return void 0;
55743
+ }
55744
+ }
55745
+ function taskSeed(kind, result) {
55746
+ const structured = resultValue(result);
55593
55747
  if (!structured || result.isError === true) return void 0;
55748
+ if (kind === "paa_harvest") {
55749
+ const operationId2 = structured.job_id ?? structured.jobId;
55750
+ if (typeof operationId2 !== "string" || !["pending", "running"].includes(String(structured.status))) return void 0;
55751
+ return { kind, operationId: operationId2, statusMessage: "PAA harvest started. Progress will be preserved while the client polls this Task." };
55752
+ }
55594
55753
  if (kind === "site_export") {
55595
55754
  const operationId2 = structured.jobId;
55596
55755
  if (typeof operationId2 !== "string" || structured.status !== "pending") return void 0;
@@ -55621,10 +55780,13 @@ function taskBase(payload, taskId) {
55621
55780
  };
55622
55781
  }
55623
55782
  function statusFrom(result) {
55624
- const status = objectValue(result.structuredContent)?.status;
55783
+ const status = resultValue(result)?.status;
55625
55784
  return typeof status === "string" ? status : void 0;
55626
55785
  }
55627
55786
  async function pollTask(executor, payload) {
55787
+ if (payload.kind === "paa_harvest") {
55788
+ return executor.harvestPaaStatus({ jobId: payload.operationId });
55789
+ }
55628
55790
  if (payload.kind === "site_export") {
55629
55791
  return executor.checkSiteExport({ jobId: payload.operationId });
55630
55792
  }
@@ -55636,6 +55798,30 @@ async function pollTask(executor, payload) {
55636
55798
  }
55637
55799
  return executor.videoFrameAnalysisStatus({ runId: payload.operationId });
55638
55800
  }
55801
+ function taskProgressMessage(kind, result, status) {
55802
+ const value = resultValue(result) ?? {};
55803
+ if (kind === "paa_harvest") {
55804
+ const resultRecord = objectValue(value.result) ?? {};
55805
+ const progress = objectValue(resultRecord.progress) ?? {};
55806
+ const records = Array.isArray(progress.records) ? progress.records.length : 0;
55807
+ const attempts = Array.isArray(value.attempts) ? value.attempts.length : 0;
55808
+ const pieces = [records > 0 ? `${records} questions preserved` : null, attempts > 0 ? `${attempts} attempts recorded` : null].filter(Boolean);
55809
+ return pieces.length > 0 ? `PAA harvest is ${status ?? "running"}: ${pieces.join(", ")}.` : `PAA harvest is ${status ?? "running"}; no question checkpoint has been persisted yet.`;
55810
+ }
55811
+ if (kind === "lead_list_enrichment") {
55812
+ const progress = objectValue(value.progress) ?? {};
55813
+ const completed = typeof progress.completedRows === "number" ? progress.completedRows : null;
55814
+ const total = typeof progress.totalRows === "number" ? progress.totalRows : null;
55815
+ if (completed !== null && total !== null) return `Lead list enrichment is ${status ?? "running"}: ${completed}/${total} rows completed.`;
55816
+ }
55817
+ if (kind === "directory_workflow") {
55818
+ const progress = objectValue(value.progress) ?? {};
55819
+ const completed = typeof progress.completedCities === "number" ? progress.completedCities : null;
55820
+ const total = typeof progress.totalCities === "number" ? progress.totalCities : null;
55821
+ if (completed !== null && total !== null) return `Directory workflow is ${status ?? "running"}: ${completed}/${total} markets completed.`;
55822
+ }
55823
+ return status ? `Underlying operation status: ${status}.` : "Underlying operation is running.";
55824
+ }
55639
55825
  async function cancelTask(executor, payload) {
55640
55826
  if (payload.kind !== "lead_list_enrichment") return;
55641
55827
  const result = await executor.leadListEnrichCancel({ jobId: payload.operationId });
@@ -55652,7 +55838,7 @@ async function detailedTask(executor, taskId, options) {
55652
55838
  return {
55653
55839
  ...base,
55654
55840
  status: "working",
55655
- statusMessage: status ? `Underlying operation status: ${status}.` : "Underlying operation is running."
55841
+ statusMessage: taskProgressMessage(payload.kind, result, status)
55656
55842
  };
55657
55843
  }
55658
55844
  if (payload.kind === "lead_list_enrichment" && status === "failed") {
@@ -55663,6 +55849,22 @@ async function detailedTask(executor, taskId, options) {
55663
55849
  result
55664
55850
  };
55665
55851
  }
55852
+ if (payload.kind === "paa_harvest" && status === "failed") {
55853
+ return {
55854
+ ...base,
55855
+ status: "failed",
55856
+ statusMessage: "PAA harvest failed; persisted attempts and billing state are included in the result.",
55857
+ result
55858
+ };
55859
+ }
55860
+ if (payload.kind === "paa_harvest" && status === "cancelled") {
55861
+ return {
55862
+ ...base,
55863
+ status: "cancelled",
55864
+ statusMessage: "PAA harvest was cancelled.",
55865
+ result
55866
+ };
55867
+ }
55666
55868
  if (payload.kind === "lead_list_enrichment" && status === "cancelled") {
55667
55869
  return {
55668
55870
  ...base,
@@ -55679,6 +55881,7 @@ async function detailedTask(executor, taskId, options) {
55679
55881
  };
55680
55882
  }
55681
55883
  function isWorking(kind, status) {
55884
+ if (kind === "paa_harvest") return status === "pending" || status === "running";
55682
55885
  if (kind === "site_export") return status === "pending" || status === "running";
55683
55886
  if (kind === "directory_workflow") return status === "queued" || status === "running";
55684
55887
  if (kind === "lead_list_enrichment") return status === "queued" || status === "running";
@@ -55710,6 +55913,9 @@ function registerMcpTasksExtension(server, executor, options) {
55710
55913
  return {};
55711
55914
  });
55712
55915
  return {
55916
+ supportsTasks() {
55917
+ return hasTasksCapability(server.server.getClientCapabilities());
55918
+ },
55713
55919
  taskify(kind, result) {
55714
55920
  if (!hasTasksCapability(server.server.getClientCapabilities())) return result;
55715
55921
  const seed = taskSeed(kind, result);
@@ -56266,7 +56472,12 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56266
56472
  inputSchema: harvestPaaInputSchema,
56267
56473
  outputSchema: recordOutputSchema("harvest_paa", HarvestPaaOutputSchema),
56268
56474
  annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
56269
- }, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
56475
+ }, async (input, requestContext) => {
56476
+ if (tasks.supportsTasks()) {
56477
+ return tasks.taskify("paa_harvest", await executor.startHarvestPaa(input), requestContext);
56478
+ }
56479
+ return formatHarvestPaa(await executor.harvestPaa(input), input);
56480
+ });
56270
56481
  server.registerTool("search_serp", {
56271
56482
  title: "Google SERP Lookup",
56272
56483
  description: `Google SERP lookup without PAA expansion. Defaults to organic rankings and Google entity IDs; request local pack, forums, videos, AI surfaces, and What People Are Saying with individual include flags or includeAllSerpFeatures at the same product price. Use gl for country and location only when city or regional context matters. Costs ${SERP_SEARCH_CREDITS} Credits per search. Call credits_info for current pricing and balance.`,
@@ -56744,7 +56955,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
56744
56955
  }, async (input) => formatLeadListImport(await executor.leadListImport(input)));
56745
56956
  server.registerTool("lead_list_enrich", {
56746
56957
  title: "Enrich a Lead List",
56747
- description: "Enrich 1\u2013250 mapped business or person rows, supplied inline or by leadListId, with matched public website, phone, email, social, contact-form, and optional associated-person evidence. Use when the list already exists; to inspect a file first use lead_list_import, to discover businesses from a niche plus city use workflow_run with get-leads, and for candidate-only Maps discovery use maps_search. Starts an owner-scoped durable Task, bills underlying Maps/page/SERP attempts with unused hold refunded, and retains requested CSV/XLSX results seven days. Opt-in email snippet recovery costs at most one SERP search per unresolved row. Opt-in peopleDiscovery costs one role search plus at most maxPeoplePerLead LinkedIn result searches per business row; it never opens LinkedIn pages and returns public role/profile candidates with separate provenance, not verified employment. Public email occurrence is not deliverability or consent verification and is not permission to contact. A public role or LinkedIn occurrence is not verified current employment. Reuse idempotencyKey only to retry the same intended job.",
56958
+ description: "Enrich 1\u2013250 mapped business or person rows, supplied inline or by leadListId, with matched public website, phone, email, social, contact-form, and optional associated-person evidence. Use when the list already exists; to inspect a file first use lead_list_import, to discover businesses from a niche plus city use workflow_run with get-leads, and for candidate-only Maps discovery use maps_search. Starts an owner-scoped durable Task, bills underlying Maps/page/SERP attempts with unused hold refunded, and retains requested CSV/XLSX results seven days. Opt-in email snippet recovery costs at most one SERP search per unresolved row. For peopleDiscovery, author one-to-three focused peopleQueryTemplates with one role intent per query; each role query reads AI Overview plus organic results, then at most maxPeoplePerLead company-matched LinkedIn result searches run without opening LinkedIn pages. Public email occurrence is not deliverability or consent verification and is not permission to contact. A public role or LinkedIn occurrence is not verified current employment. Reuse idempotencyKey only to retry the same intended job.",
56748
56959
  inputSchema: LeadListEnrichInputSchema,
56749
56960
  outputSchema: recordOutputSchema("lead_list_enrich", LeadListEnrichmentOutputSchema),
56750
56961
  annotations: { title: "Enrich a Lead List", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
@@ -57267,7 +57478,7 @@ var init_http_mcp_tool_executor = __esm({
57267
57478
  retryable: true,
57268
57479
  path: path6,
57269
57480
  timeoutMs,
57270
- message: `MCP Scraper request exceeded ${Math.round(timeoutMs / 1e3)}s and was cancelled client-side. The operation may still be running or may have already completed and been billed on our side. Retry with fewer results or use the async API for deep harvests. ${timeoutGuidance}`
57481
+ message: `MCP Scraper stopped waiting after ${Math.round(timeoutMs / 1e3)}s. This does not prove server-side work stopped: the operation may still be running or may have already completed and been billed. Prefer the returned Task or job handle for long work. ${timeoutGuidance}`
57271
57482
  })
57272
57483
  }],
57273
57484
  isError: true
@@ -57353,6 +57564,13 @@ var init_http_mcp_tool_executor = __esm({
57353
57564
  const headers = input.idempotencyKey ? { "Idempotency-Key": input.idempotencyKey } : {};
57354
57565
  return this.call("/harvest/sync", input, timeoutMs, "POST", headers, true);
57355
57566
  }
57567
+ startHarvestPaa(input) {
57568
+ const headers = input.idempotencyKey ? { "Idempotency-Key": input.idempotencyKey } : {};
57569
+ return this.call("/harvest", input, this.timeoutMs, "POST", headers, true);
57570
+ }
57571
+ harvestPaaStatus(input) {
57572
+ return this.getJson(`/jobs/${encodeURIComponent(input.jobId)}`);
57573
+ }
57356
57574
  searchSerp(input) {
57357
57575
  const timeoutMs = this.httpTimeoutOverrideMs ?? harvestTimeoutBudget(0, true).clientMs;
57358
57576
  const headers = input.idempotencyKey ? { "Idempotency-Key": input.idempotencyKey } : {};
@@ -59585,6 +59803,1539 @@ var init_browser_agent_mcp_server = __esm({
59585
59803
  }
59586
59804
  });
59587
59805
 
59806
+ // src/mcp/memory-image-tool-schemas.generated.ts
59807
+ var MEMORY_IMAGE_TOOL_SCHEMAS;
59808
+ var init_memory_image_tool_schemas_generated = __esm({
59809
+ "src/mcp/memory-image-tool-schemas.generated.ts"() {
59810
+ "use strict";
59811
+ MEMORY_IMAGE_TOOL_SCHEMAS = [
59812
+ {
59813
+ "id": "image_project_create",
59814
+ "upstreamName": "image_project_create",
59815
+ "description": "Create a stable image project inside a writable hosted Memory vault. Projects organize governed private image assets; they do not create local folders or change object keys.",
59816
+ "inputJsonSchema": {
59817
+ "type": "object",
59818
+ "properties": {
59819
+ "vault": {
59820
+ "type": "string",
59821
+ "description": "Writable logical Memory vault. Defaults to the active vault."
59822
+ },
59823
+ "name": {
59824
+ "type": "string",
59825
+ "minLength": 1,
59826
+ "maxLength": 120
59827
+ },
59828
+ "description": {
59829
+ "type": "string",
59830
+ "maxLength": 2e3
59831
+ }
59832
+ },
59833
+ "required": [
59834
+ "name"
59835
+ ],
59836
+ "additionalProperties": false,
59837
+ "$schema": "http://json-schema.org/draft-07/schema#"
59838
+ },
59839
+ "outputJsonSchema": {
59840
+ "type": "object",
59841
+ "properties": {
59842
+ "ok": {
59843
+ "type": "boolean",
59844
+ "enum": [
59845
+ true,
59846
+ false
59847
+ ]
59848
+ },
59849
+ "project": {
59850
+ "type": "object",
59851
+ "properties": {
59852
+ "id": {
59853
+ "type": "string"
59854
+ },
59855
+ "name": {
59856
+ "type": "string"
59857
+ },
59858
+ "description": {
59859
+ "type": [
59860
+ "string",
59861
+ "null"
59862
+ ]
59863
+ },
59864
+ "createdAt": {
59865
+ "type": "string"
59866
+ }
59867
+ },
59868
+ "required": [
59869
+ "id",
59870
+ "name",
59871
+ "description",
59872
+ "createdAt"
59873
+ ],
59874
+ "additionalProperties": false
59875
+ },
59876
+ "code": {
59877
+ "type": "string"
59878
+ },
59879
+ "error": {
59880
+ "type": "string"
59881
+ }
59882
+ },
59883
+ "required": [
59884
+ "ok"
59885
+ ],
59886
+ "additionalProperties": false,
59887
+ "$schema": "http://json-schema.org/draft-07/schema#"
59888
+ },
59889
+ "annotations": {
59890
+ "title": "Create Image Project",
59891
+ "readOnlyHint": false,
59892
+ "destructiveHint": false,
59893
+ "idempotentHint": false,
59894
+ "openWorldHint": false
59895
+ }
59896
+ },
59897
+ {
59898
+ "id": "image_project_list",
59899
+ "upstreamName": "image_project_list",
59900
+ "description": "List image projects in an accessible hosted Memory vault. Returns organization metadata only, never object-store keys.",
59901
+ "inputJsonSchema": {
59902
+ "type": "object",
59903
+ "properties": {
59904
+ "vault": {
59905
+ "type": "string"
59906
+ }
59907
+ },
59908
+ "additionalProperties": false,
59909
+ "$schema": "http://json-schema.org/draft-07/schema#"
59910
+ },
59911
+ "outputJsonSchema": {
59912
+ "type": "object",
59913
+ "properties": {
59914
+ "ok": {
59915
+ "type": "boolean",
59916
+ "enum": [
59917
+ true,
59918
+ false
59919
+ ]
59920
+ },
59921
+ "projects": {
59922
+ "type": "array",
59923
+ "items": {
59924
+ "type": "object",
59925
+ "properties": {
59926
+ "id": {
59927
+ "type": "string"
59928
+ },
59929
+ "name": {
59930
+ "type": "string"
59931
+ },
59932
+ "description": {
59933
+ "type": [
59934
+ "string",
59935
+ "null"
59936
+ ]
59937
+ },
59938
+ "status": {
59939
+ "type": "string"
59940
+ },
59941
+ "createdAt": {
59942
+ "type": "string"
59943
+ },
59944
+ "updatedAt": {
59945
+ "type": "string"
59946
+ }
59947
+ },
59948
+ "required": [
59949
+ "id",
59950
+ "name",
59951
+ "description",
59952
+ "status",
59953
+ "createdAt",
59954
+ "updatedAt"
59955
+ ],
59956
+ "additionalProperties": false
59957
+ }
59958
+ },
59959
+ "code": {
59960
+ "type": "string"
59961
+ },
59962
+ "error": {
59963
+ "type": "string"
59964
+ }
59965
+ },
59966
+ "required": [
59967
+ "ok"
59968
+ ],
59969
+ "additionalProperties": false,
59970
+ "$schema": "http://json-schema.org/draft-07/schema#"
59971
+ },
59972
+ "annotations": {
59973
+ "title": "List Image Projects",
59974
+ "readOnlyHint": true,
59975
+ "destructiveHint": false,
59976
+ "idempotentHint": true,
59977
+ "openWorldHint": false
59978
+ }
59979
+ },
59980
+ {
59981
+ "id": "image_folder_create",
59982
+ "upstreamName": "image_folder_create",
59983
+ "description": "Create a nested image folder inside one writable Memory image project. Maximum nesting depth is eight; this changes logical organization only.",
59984
+ "inputJsonSchema": {
59985
+ "type": "object",
59986
+ "properties": {
59987
+ "vault": {
59988
+ "type": "string"
59989
+ },
59990
+ "projectId": {
59991
+ "type": "string",
59992
+ "minLength": 1
59993
+ },
59994
+ "parentId": {
59995
+ "type": "string",
59996
+ "minLength": 1
59997
+ },
59998
+ "name": {
59999
+ "type": "string",
60000
+ "minLength": 1,
60001
+ "maxLength": 120
60002
+ }
60003
+ },
60004
+ "required": [
60005
+ "projectId",
60006
+ "name"
60007
+ ],
60008
+ "additionalProperties": false,
60009
+ "$schema": "http://json-schema.org/draft-07/schema#"
60010
+ },
60011
+ "outputJsonSchema": {
60012
+ "type": "object",
60013
+ "properties": {
60014
+ "ok": {
60015
+ "type": "boolean",
60016
+ "enum": [
60017
+ true,
60018
+ false
60019
+ ]
60020
+ },
60021
+ "folder": {
60022
+ "type": "object",
60023
+ "properties": {
60024
+ "id": {
60025
+ "type": "string"
60026
+ },
60027
+ "projectId": {
60028
+ "type": "string"
60029
+ },
60030
+ "parentId": {
60031
+ "type": [
60032
+ "string",
60033
+ "null"
60034
+ ]
60035
+ },
60036
+ "name": {
60037
+ "type": "string"
60038
+ },
60039
+ "depth": {
60040
+ "type": "number"
60041
+ },
60042
+ "createdAt": {
60043
+ "type": "string"
60044
+ }
60045
+ },
60046
+ "required": [
60047
+ "id",
60048
+ "projectId",
60049
+ "parentId",
60050
+ "name",
60051
+ "depth",
60052
+ "createdAt"
60053
+ ],
60054
+ "additionalProperties": false
60055
+ },
60056
+ "code": {
60057
+ "type": "string"
60058
+ },
60059
+ "error": {
60060
+ "type": "string"
60061
+ }
60062
+ },
60063
+ "required": [
60064
+ "ok"
60065
+ ],
60066
+ "additionalProperties": false,
60067
+ "$schema": "http://json-schema.org/draft-07/schema#"
60068
+ },
60069
+ "annotations": {
60070
+ "title": "Create Image Folder",
60071
+ "readOnlyHint": false,
60072
+ "destructiveHint": false,
60073
+ "idempotentHint": false,
60074
+ "openWorldHint": false
60075
+ }
60076
+ },
60077
+ {
60078
+ "id": "image_folder_list",
60079
+ "upstreamName": "image_folder_list",
60080
+ "description": "List the bounded folder tree for one accessible Memory image project.",
60081
+ "inputJsonSchema": {
60082
+ "type": "object",
60083
+ "properties": {
60084
+ "vault": {
60085
+ "type": "string"
60086
+ },
60087
+ "projectId": {
60088
+ "type": "string",
60089
+ "minLength": 1
60090
+ }
60091
+ },
60092
+ "required": [
60093
+ "projectId"
60094
+ ],
60095
+ "additionalProperties": false,
60096
+ "$schema": "http://json-schema.org/draft-07/schema#"
60097
+ },
60098
+ "outputJsonSchema": {
60099
+ "type": "object",
60100
+ "properties": {
60101
+ "ok": {
60102
+ "type": "boolean",
60103
+ "enum": [
60104
+ true,
60105
+ false
60106
+ ]
60107
+ },
60108
+ "folders": {
60109
+ "type": "array",
60110
+ "items": {
60111
+ "type": "object",
60112
+ "properties": {
60113
+ "id": {
60114
+ "type": "string"
60115
+ },
60116
+ "projectId": {
60117
+ "type": "string"
60118
+ },
60119
+ "parentId": {
60120
+ "type": [
60121
+ "string",
60122
+ "null"
60123
+ ]
60124
+ },
60125
+ "name": {
60126
+ "type": "string"
60127
+ },
60128
+ "depth": {
60129
+ "type": "number"
60130
+ },
60131
+ "createdAt": {
60132
+ "type": "string"
60133
+ },
60134
+ "updatedAt": {
60135
+ "type": "string"
60136
+ }
60137
+ },
60138
+ "required": [
60139
+ "id",
60140
+ "projectId",
60141
+ "parentId",
60142
+ "name",
60143
+ "depth",
60144
+ "createdAt",
60145
+ "updatedAt"
60146
+ ],
60147
+ "additionalProperties": false
60148
+ }
60149
+ },
60150
+ "code": {
60151
+ "type": "string"
60152
+ },
60153
+ "error": {
60154
+ "type": "string"
60155
+ }
60156
+ },
60157
+ "required": [
60158
+ "ok"
60159
+ ],
60160
+ "additionalProperties": false,
60161
+ "$schema": "http://json-schema.org/draft-07/schema#"
60162
+ },
60163
+ "annotations": {
60164
+ "title": "List Image Folders",
60165
+ "readOnlyHint": true,
60166
+ "destructiveHint": false,
60167
+ "idempotentHint": true,
60168
+ "openWorldHint": false
60169
+ }
60170
+ },
60171
+ {
60172
+ "id": "image_asset_save",
60173
+ "upstreamName": "image_asset_save",
60174
+ "description": "Opt in to preserving one image in hosted Memory. Saves immutable private bytes, creates a sanitized preview, consumes storage/embedding quota, and queues visual plus useful fused embeddings. Use for durable image recall; do not use merely to view a transient scrape result.",
60175
+ "inputJsonSchema": {
60176
+ "type": "object",
60177
+ "properties": {
60178
+ "vault": {
60179
+ "type": "string"
60180
+ },
60181
+ "projectId": {
60182
+ "type": "string",
60183
+ "minLength": 1
60184
+ },
60185
+ "folderId": {
60186
+ "type": "string",
60187
+ "minLength": 1
60188
+ },
60189
+ "sourceUrl": {
60190
+ "type": "string",
60191
+ "format": "uri",
60192
+ "description": "Public HTTPS image URL. Private-network and redirect targets are rejected."
60193
+ },
60194
+ "artifactId": {
60195
+ "type": "string",
60196
+ "minLength": 1,
60197
+ "description": "Owned MCP Scraper artifact ID. Available through the aggregate MCP handoff."
60198
+ },
60199
+ "imageBase64": {
60200
+ "type": "string",
60201
+ "minLength": 1,
60202
+ "description": "Bounded base64 image for clients without an upload transport; maximum decoded size is 20 MiB."
60203
+ },
60204
+ "title": {
60205
+ "type": "string",
60206
+ "minLength": 1,
60207
+ "maxLength": 240
60208
+ },
60209
+ "description": {
60210
+ "type": "string",
60211
+ "maxLength": 4e3
60212
+ },
60213
+ "altText": {
60214
+ "type": "string",
60215
+ "maxLength": 2e3
60216
+ },
60217
+ "tags": {
60218
+ "type": "array",
60219
+ "items": {
60220
+ "type": "string",
60221
+ "minLength": 1,
60222
+ "maxLength": 80
60223
+ },
60224
+ "maxItems": 20
60225
+ },
60226
+ "sourceRef": {
60227
+ "type": "object",
60228
+ "additionalProperties": {
60229
+ "anyOf": [
60230
+ {
60231
+ "type": "string",
60232
+ "maxLength": 1e3
60233
+ },
60234
+ {
60235
+ "type": "number"
60236
+ },
60237
+ {
60238
+ "type": "boolean"
60239
+ },
60240
+ {
60241
+ "type": "null"
60242
+ }
60243
+ ]
60244
+ }
60245
+ },
60246
+ "idempotencyKey": {
60247
+ "type": "string",
60248
+ "minLength": 8,
60249
+ "maxLength": 200,
60250
+ "description": "Stable key for this logical save. Retrying the same request returns the same asset without another write."
60251
+ }
60252
+ },
60253
+ "required": [
60254
+ "title",
60255
+ "idempotencyKey"
60256
+ ],
60257
+ "additionalProperties": false,
60258
+ "$schema": "http://json-schema.org/draft-07/schema#"
60259
+ },
60260
+ "outputJsonSchema": {
60261
+ "type": "object",
60262
+ "properties": {
60263
+ "ok": {
60264
+ "type": "boolean",
60265
+ "enum": [
60266
+ true,
60267
+ false
60268
+ ]
60269
+ },
60270
+ "asset": {
60271
+ "type": "object",
60272
+ "properties": {
60273
+ "assetId": {
60274
+ "type": "string"
60275
+ },
60276
+ "title": {
60277
+ "type": "string"
60278
+ },
60279
+ "description": {
60280
+ "type": [
60281
+ "string",
60282
+ "null"
60283
+ ]
60284
+ },
60285
+ "altText": {
60286
+ "type": [
60287
+ "string",
60288
+ "null"
60289
+ ]
60290
+ },
60291
+ "sourceUrl": {
60292
+ "type": [
60293
+ "string",
60294
+ "null"
60295
+ ]
60296
+ },
60297
+ "sourceKind": {
60298
+ "type": "string"
60299
+ },
60300
+ "mimeType": {
60301
+ "type": "string"
60302
+ },
60303
+ "bytes": {
60304
+ "type": "number"
60305
+ },
60306
+ "width": {
60307
+ "type": "number"
60308
+ },
60309
+ "height": {
60310
+ "type": "number"
60311
+ },
60312
+ "projectId": {
60313
+ "type": [
60314
+ "string",
60315
+ "null"
60316
+ ]
60317
+ },
60318
+ "folderId": {
60319
+ "type": [
60320
+ "string",
60321
+ "null"
60322
+ ]
60323
+ },
60324
+ "tags": {
60325
+ "type": "array",
60326
+ "items": {
60327
+ "type": "string"
60328
+ }
60329
+ },
60330
+ "status": {
60331
+ "type": "string"
60332
+ },
60333
+ "embeddingStatus": {
60334
+ "type": "string"
60335
+ },
60336
+ "createdAt": {
60337
+ "type": "string"
60338
+ },
60339
+ "updatedAt": {
60340
+ "type": "string"
60341
+ },
60342
+ "preview": {
60343
+ "type": "object",
60344
+ "properties": {
60345
+ "url": {
60346
+ "type": "string",
60347
+ "format": "uri"
60348
+ },
60349
+ "expiresAt": {
60350
+ "type": "string",
60351
+ "format": "date-time"
60352
+ },
60353
+ "mimeType": {
60354
+ "type": "string",
60355
+ "const": "image/webp"
60356
+ }
60357
+ },
60358
+ "required": [
60359
+ "url",
60360
+ "expiresAt",
60361
+ "mimeType"
60362
+ ],
60363
+ "additionalProperties": false
60364
+ }
60365
+ },
60366
+ "required": [
60367
+ "assetId",
60368
+ "title",
60369
+ "description",
60370
+ "altText",
60371
+ "sourceUrl",
60372
+ "sourceKind",
60373
+ "mimeType",
60374
+ "bytes",
60375
+ "width",
60376
+ "height",
60377
+ "projectId",
60378
+ "folderId",
60379
+ "tags",
60380
+ "status",
60381
+ "embeddingStatus",
60382
+ "createdAt",
60383
+ "updatedAt"
60384
+ ],
60385
+ "additionalProperties": false
60386
+ },
60387
+ "reusedObject": {
60388
+ "type": "boolean"
60389
+ },
60390
+ "nextAction": {
60391
+ "type": "string"
60392
+ },
60393
+ "code": {
60394
+ "type": "string"
60395
+ },
60396
+ "error": {
60397
+ "type": "string"
60398
+ }
60399
+ },
60400
+ "required": [
60401
+ "ok"
60402
+ ],
60403
+ "additionalProperties": false,
60404
+ "$schema": "http://json-schema.org/draft-07/schema#"
60405
+ },
60406
+ "annotations": {
60407
+ "title": "Save Image Asset",
60408
+ "readOnlyHint": false,
60409
+ "destructiveHint": false,
60410
+ "idempotentHint": true,
60411
+ "openWorldHint": true
60412
+ }
60413
+ },
60414
+ {
60415
+ "id": "image_asset_get",
60416
+ "upstreamName": "image_asset_get",
60417
+ "description": "Read one authorized private image asset and optionally mint a five-minute preview URL. The URL is never persisted; call again after expiry.",
60418
+ "inputJsonSchema": {
60419
+ "type": "object",
60420
+ "properties": {
60421
+ "vault": {
60422
+ "type": "string"
60423
+ },
60424
+ "assetId": {
60425
+ "type": "string",
60426
+ "minLength": 1
60427
+ },
60428
+ "includePreview": {
60429
+ "type": "boolean",
60430
+ "default": true
60431
+ }
60432
+ },
60433
+ "required": [
60434
+ "assetId"
60435
+ ],
60436
+ "additionalProperties": false,
60437
+ "$schema": "http://json-schema.org/draft-07/schema#"
60438
+ },
60439
+ "outputJsonSchema": {
60440
+ "type": "object",
60441
+ "properties": {
60442
+ "ok": {
60443
+ "type": "boolean",
60444
+ "enum": [
60445
+ true,
60446
+ false
60447
+ ]
60448
+ },
60449
+ "asset": {
60450
+ "type": "object",
60451
+ "properties": {
60452
+ "assetId": {
60453
+ "type": "string"
60454
+ },
60455
+ "title": {
60456
+ "type": "string"
60457
+ },
60458
+ "description": {
60459
+ "type": [
60460
+ "string",
60461
+ "null"
60462
+ ]
60463
+ },
60464
+ "altText": {
60465
+ "type": [
60466
+ "string",
60467
+ "null"
60468
+ ]
60469
+ },
60470
+ "sourceUrl": {
60471
+ "type": [
60472
+ "string",
60473
+ "null"
60474
+ ]
60475
+ },
60476
+ "sourceKind": {
60477
+ "type": "string"
60478
+ },
60479
+ "mimeType": {
60480
+ "type": "string"
60481
+ },
60482
+ "bytes": {
60483
+ "type": "number"
60484
+ },
60485
+ "width": {
60486
+ "type": "number"
60487
+ },
60488
+ "height": {
60489
+ "type": "number"
60490
+ },
60491
+ "projectId": {
60492
+ "type": [
60493
+ "string",
60494
+ "null"
60495
+ ]
60496
+ },
60497
+ "folderId": {
60498
+ "type": [
60499
+ "string",
60500
+ "null"
60501
+ ]
60502
+ },
60503
+ "tags": {
60504
+ "type": "array",
60505
+ "items": {
60506
+ "type": "string"
60507
+ }
60508
+ },
60509
+ "status": {
60510
+ "type": "string"
60511
+ },
60512
+ "embeddingStatus": {
60513
+ "type": "string"
60514
+ },
60515
+ "createdAt": {
60516
+ "type": "string"
60517
+ },
60518
+ "updatedAt": {
60519
+ "type": "string"
60520
+ },
60521
+ "preview": {
60522
+ "type": "object",
60523
+ "properties": {
60524
+ "url": {
60525
+ "type": "string",
60526
+ "format": "uri"
60527
+ },
60528
+ "expiresAt": {
60529
+ "type": "string",
60530
+ "format": "date-time"
60531
+ },
60532
+ "mimeType": {
60533
+ "type": "string",
60534
+ "const": "image/webp"
60535
+ }
60536
+ },
60537
+ "required": [
60538
+ "url",
60539
+ "expiresAt",
60540
+ "mimeType"
60541
+ ],
60542
+ "additionalProperties": false
60543
+ }
60544
+ },
60545
+ "required": [
60546
+ "assetId",
60547
+ "title",
60548
+ "description",
60549
+ "altText",
60550
+ "sourceUrl",
60551
+ "sourceKind",
60552
+ "mimeType",
60553
+ "bytes",
60554
+ "width",
60555
+ "height",
60556
+ "projectId",
60557
+ "folderId",
60558
+ "tags",
60559
+ "status",
60560
+ "embeddingStatus",
60561
+ "createdAt",
60562
+ "updatedAt"
60563
+ ],
60564
+ "additionalProperties": false
60565
+ },
60566
+ "code": {
60567
+ "type": "string"
60568
+ },
60569
+ "error": {
60570
+ "type": "string"
60571
+ }
60572
+ },
60573
+ "required": [
60574
+ "ok"
60575
+ ],
60576
+ "additionalProperties": false,
60577
+ "$schema": "http://json-schema.org/draft-07/schema#"
60578
+ },
60579
+ "annotations": {
60580
+ "title": "Get Image Asset",
60581
+ "readOnlyHint": true,
60582
+ "destructiveHint": false,
60583
+ "idempotentHint": true,
60584
+ "openWorldHint": false
60585
+ }
60586
+ },
60587
+ {
60588
+ "id": "image_asset_list",
60589
+ "upstreamName": "image_asset_list",
60590
+ "description": "List authorized private image metadata with exact location, provenance, media, tag, and creation-time filters. Use image_asset_search for semantic retrieval.",
60591
+ "inputJsonSchema": {
60592
+ "type": "object",
60593
+ "properties": {
60594
+ "vault": {
60595
+ "type": "string"
60596
+ },
60597
+ "projectId": {
60598
+ "type": "string"
60599
+ },
60600
+ "folderId": {
60601
+ "type": "string"
60602
+ },
60603
+ "sourceKind": {
60604
+ "type": "string",
60605
+ "enum": [
60606
+ "upload",
60607
+ "scrape",
60608
+ "screenshot",
60609
+ "instagram",
60610
+ "external_url",
60611
+ "import"
60612
+ ]
60613
+ },
60614
+ "sourceHost": {
60615
+ "type": "string",
60616
+ "minLength": 1,
60617
+ "maxLength": 253
60618
+ },
60619
+ "mimeType": {
60620
+ "type": "string",
60621
+ "enum": [
60622
+ "image/jpeg",
60623
+ "image/png",
60624
+ "image/webp",
60625
+ "image/gif"
60626
+ ]
60627
+ },
60628
+ "tags": {
60629
+ "type": "array",
60630
+ "items": {
60631
+ "type": "string",
60632
+ "minLength": 1,
60633
+ "maxLength": 80
60634
+ },
60635
+ "maxItems": 20
60636
+ },
60637
+ "createdAfter": {
60638
+ "type": "string",
60639
+ "format": "date-time"
60640
+ },
60641
+ "createdBefore": {
60642
+ "type": "string",
60643
+ "format": "date-time"
60644
+ },
60645
+ "limit": {
60646
+ "type": "integer",
60647
+ "minimum": 1,
60648
+ "maximum": 100,
60649
+ "default": 30
60650
+ }
60651
+ },
60652
+ "additionalProperties": false,
60653
+ "$schema": "http://json-schema.org/draft-07/schema#"
60654
+ },
60655
+ "outputJsonSchema": {
60656
+ "type": "object",
60657
+ "properties": {
60658
+ "ok": {
60659
+ "type": "boolean",
60660
+ "enum": [
60661
+ true,
60662
+ false
60663
+ ]
60664
+ },
60665
+ "assets": {
60666
+ "type": "array",
60667
+ "items": {
60668
+ "type": "object",
60669
+ "properties": {
60670
+ "assetId": {
60671
+ "type": "string"
60672
+ },
60673
+ "title": {
60674
+ "type": "string"
60675
+ },
60676
+ "description": {
60677
+ "type": [
60678
+ "string",
60679
+ "null"
60680
+ ]
60681
+ },
60682
+ "altText": {
60683
+ "type": [
60684
+ "string",
60685
+ "null"
60686
+ ]
60687
+ },
60688
+ "sourceUrl": {
60689
+ "type": [
60690
+ "string",
60691
+ "null"
60692
+ ]
60693
+ },
60694
+ "sourceKind": {
60695
+ "type": "string"
60696
+ },
60697
+ "mimeType": {
60698
+ "type": "string"
60699
+ },
60700
+ "bytes": {
60701
+ "type": "number"
60702
+ },
60703
+ "width": {
60704
+ "type": "number"
60705
+ },
60706
+ "height": {
60707
+ "type": "number"
60708
+ },
60709
+ "projectId": {
60710
+ "type": [
60711
+ "string",
60712
+ "null"
60713
+ ]
60714
+ },
60715
+ "folderId": {
60716
+ "type": [
60717
+ "string",
60718
+ "null"
60719
+ ]
60720
+ },
60721
+ "tags": {
60722
+ "type": "array",
60723
+ "items": {
60724
+ "type": "string"
60725
+ }
60726
+ },
60727
+ "status": {
60728
+ "type": "string"
60729
+ },
60730
+ "embeddingStatus": {
60731
+ "type": "string"
60732
+ },
60733
+ "createdAt": {
60734
+ "type": "string"
60735
+ },
60736
+ "updatedAt": {
60737
+ "type": "string"
60738
+ },
60739
+ "preview": {
60740
+ "type": "object",
60741
+ "properties": {
60742
+ "url": {
60743
+ "type": "string",
60744
+ "format": "uri"
60745
+ },
60746
+ "expiresAt": {
60747
+ "type": "string",
60748
+ "format": "date-time"
60749
+ },
60750
+ "mimeType": {
60751
+ "type": "string",
60752
+ "const": "image/webp"
60753
+ }
60754
+ },
60755
+ "required": [
60756
+ "url",
60757
+ "expiresAt",
60758
+ "mimeType"
60759
+ ],
60760
+ "additionalProperties": false
60761
+ }
60762
+ },
60763
+ "required": [
60764
+ "assetId",
60765
+ "title",
60766
+ "description",
60767
+ "altText",
60768
+ "sourceUrl",
60769
+ "sourceKind",
60770
+ "mimeType",
60771
+ "bytes",
60772
+ "width",
60773
+ "height",
60774
+ "projectId",
60775
+ "folderId",
60776
+ "tags",
60777
+ "status",
60778
+ "embeddingStatus",
60779
+ "createdAt",
60780
+ "updatedAt"
60781
+ ],
60782
+ "additionalProperties": false
60783
+ }
60784
+ },
60785
+ "count": {
60786
+ "type": "number"
60787
+ },
60788
+ "code": {
60789
+ "type": "string"
60790
+ },
60791
+ "error": {
60792
+ "type": "string"
60793
+ }
60794
+ },
60795
+ "required": [
60796
+ "ok"
60797
+ ],
60798
+ "additionalProperties": false,
60799
+ "$schema": "http://json-schema.org/draft-07/schema#"
60800
+ },
60801
+ "annotations": {
60802
+ "title": "List Image Assets",
60803
+ "readOnlyHint": true,
60804
+ "destructiveHint": false,
60805
+ "idempotentHint": true,
60806
+ "openWorldHint": false
60807
+ }
60808
+ },
60809
+ {
60810
+ "id": "image_asset_search",
60811
+ "upstreamName": "image_asset_search",
60812
+ "description": "Semantically search authorized private image Memory using text, a stored asset, a public HTTPS image, or bounded base64. Text plus one image performs a fused query. This consumes query-embedding usage.",
60813
+ "inputJsonSchema": {
60814
+ "type": "object",
60815
+ "properties": {
60816
+ "vault": {
60817
+ "type": "string"
60818
+ },
60819
+ "queryText": {
60820
+ "type": "string",
60821
+ "minLength": 1,
60822
+ "maxLength": 4e3
60823
+ },
60824
+ "queryAssetId": {
60825
+ "type": "string",
60826
+ "minLength": 1
60827
+ },
60828
+ "queryUrl": {
60829
+ "type": "string",
60830
+ "format": "uri"
60831
+ },
60832
+ "queryImageBase64": {
60833
+ "type": "string",
60834
+ "minLength": 1
60835
+ },
60836
+ "projectId": {
60837
+ "type": "string"
60838
+ },
60839
+ "folderId": {
60840
+ "type": "string"
60841
+ },
60842
+ "sourceKind": {
60843
+ "type": "string",
60844
+ "enum": [
60845
+ "upload",
60846
+ "scrape",
60847
+ "screenshot",
60848
+ "instagram",
60849
+ "external_url",
60850
+ "import"
60851
+ ]
60852
+ },
60853
+ "sourceHost": {
60854
+ "type": "string",
60855
+ "minLength": 1,
60856
+ "maxLength": 253
60857
+ },
60858
+ "mimeType": {
60859
+ "type": "string",
60860
+ "enum": [
60861
+ "image/jpeg",
60862
+ "image/png",
60863
+ "image/webp",
60864
+ "image/gif"
60865
+ ]
60866
+ },
60867
+ "tags": {
60868
+ "type": "array",
60869
+ "items": {
60870
+ "type": "string",
60871
+ "minLength": 1,
60872
+ "maxLength": 80
60873
+ },
60874
+ "maxItems": 20
60875
+ },
60876
+ "createdAfter": {
60877
+ "type": "string",
60878
+ "format": "date-time"
60879
+ },
60880
+ "createdBefore": {
60881
+ "type": "string",
60882
+ "format": "date-time"
60883
+ },
60884
+ "limit": {
60885
+ "type": "integer",
60886
+ "minimum": 1,
60887
+ "maximum": 30,
60888
+ "default": 10
60889
+ },
60890
+ "includePreview": {
60891
+ "type": "boolean",
60892
+ "default": false
60893
+ }
60894
+ },
60895
+ "additionalProperties": false,
60896
+ "$schema": "http://json-schema.org/draft-07/schema#"
60897
+ },
60898
+ "outputJsonSchema": {
60899
+ "type": "object",
60900
+ "properties": {
60901
+ "ok": {
60902
+ "type": "boolean",
60903
+ "enum": [
60904
+ true,
60905
+ false
60906
+ ]
60907
+ },
60908
+ "results": {
60909
+ "type": "array",
60910
+ "items": {
60911
+ "type": "object",
60912
+ "properties": {
60913
+ "asset": {
60914
+ "type": "object",
60915
+ "properties": {
60916
+ "assetId": {
60917
+ "type": "string"
60918
+ },
60919
+ "title": {
60920
+ "type": "string"
60921
+ },
60922
+ "description": {
60923
+ "type": [
60924
+ "string",
60925
+ "null"
60926
+ ]
60927
+ },
60928
+ "altText": {
60929
+ "type": [
60930
+ "string",
60931
+ "null"
60932
+ ]
60933
+ },
60934
+ "sourceUrl": {
60935
+ "type": [
60936
+ "string",
60937
+ "null"
60938
+ ]
60939
+ },
60940
+ "sourceKind": {
60941
+ "type": "string"
60942
+ },
60943
+ "mimeType": {
60944
+ "type": "string"
60945
+ },
60946
+ "bytes": {
60947
+ "type": "number"
60948
+ },
60949
+ "width": {
60950
+ "type": "number"
60951
+ },
60952
+ "height": {
60953
+ "type": "number"
60954
+ },
60955
+ "projectId": {
60956
+ "type": [
60957
+ "string",
60958
+ "null"
60959
+ ]
60960
+ },
60961
+ "folderId": {
60962
+ "type": [
60963
+ "string",
60964
+ "null"
60965
+ ]
60966
+ },
60967
+ "tags": {
60968
+ "type": "array",
60969
+ "items": {
60970
+ "type": "string"
60971
+ }
60972
+ },
60973
+ "status": {
60974
+ "type": "string"
60975
+ },
60976
+ "embeddingStatus": {
60977
+ "type": "string"
60978
+ },
60979
+ "createdAt": {
60980
+ "type": "string"
60981
+ },
60982
+ "updatedAt": {
60983
+ "type": "string"
60984
+ },
60985
+ "preview": {
60986
+ "type": "object",
60987
+ "properties": {
60988
+ "url": {
60989
+ "type": "string",
60990
+ "format": "uri"
60991
+ },
60992
+ "expiresAt": {
60993
+ "type": "string",
60994
+ "format": "date-time"
60995
+ },
60996
+ "mimeType": {
60997
+ "type": "string",
60998
+ "const": "image/webp"
60999
+ }
61000
+ },
61001
+ "required": [
61002
+ "url",
61003
+ "expiresAt",
61004
+ "mimeType"
61005
+ ],
61006
+ "additionalProperties": false
61007
+ }
61008
+ },
61009
+ "required": [
61010
+ "assetId",
61011
+ "title",
61012
+ "description",
61013
+ "altText",
61014
+ "sourceUrl",
61015
+ "sourceKind",
61016
+ "mimeType",
61017
+ "bytes",
61018
+ "width",
61019
+ "height",
61020
+ "projectId",
61021
+ "folderId",
61022
+ "tags",
61023
+ "status",
61024
+ "embeddingStatus",
61025
+ "createdAt",
61026
+ "updatedAt"
61027
+ ],
61028
+ "additionalProperties": false
61029
+ },
61030
+ "score": {
61031
+ "type": "number"
61032
+ },
61033
+ "matchRepresentations": {
61034
+ "type": "array",
61035
+ "items": {
61036
+ "type": "string",
61037
+ "enum": [
61038
+ "visual",
61039
+ "fused"
61040
+ ]
61041
+ }
61042
+ },
61043
+ "embeddingModel": {
61044
+ "type": "string"
61045
+ }
61046
+ },
61047
+ "required": [
61048
+ "asset",
61049
+ "score",
61050
+ "matchRepresentations",
61051
+ "embeddingModel"
61052
+ ],
61053
+ "additionalProperties": false
61054
+ }
61055
+ },
61056
+ "count": {
61057
+ "type": "number"
61058
+ },
61059
+ "code": {
61060
+ "type": "string"
61061
+ },
61062
+ "error": {
61063
+ "type": "string"
61064
+ }
61065
+ },
61066
+ "required": [
61067
+ "ok"
61068
+ ],
61069
+ "additionalProperties": false,
61070
+ "$schema": "http://json-schema.org/draft-07/schema#"
61071
+ },
61072
+ "annotations": {
61073
+ "title": "Search Image Assets",
61074
+ "readOnlyHint": true,
61075
+ "destructiveHint": false,
61076
+ "idempotentHint": true,
61077
+ "openWorldHint": true
61078
+ }
61079
+ },
61080
+ {
61081
+ "id": "image_asset_move",
61082
+ "upstreamName": "image_asset_move",
61083
+ "description": "Move an image asset between project/folder locations in the same writable Memory vault. This updates metadata only and never copies private image bytes.",
61084
+ "inputJsonSchema": {
61085
+ "type": "object",
61086
+ "properties": {
61087
+ "vault": {
61088
+ "type": "string"
61089
+ },
61090
+ "assetId": {
61091
+ "type": "string",
61092
+ "minLength": 1
61093
+ },
61094
+ "projectId": {
61095
+ "anyOf": [
61096
+ {
61097
+ "type": "string",
61098
+ "minLength": 1
61099
+ },
61100
+ {
61101
+ "type": "null"
61102
+ }
61103
+ ]
61104
+ },
61105
+ "folderId": {
61106
+ "anyOf": [
61107
+ {
61108
+ "type": "string",
61109
+ "minLength": 1
61110
+ },
61111
+ {
61112
+ "type": "null"
61113
+ }
61114
+ ]
61115
+ }
61116
+ },
61117
+ "required": [
61118
+ "assetId"
61119
+ ],
61120
+ "additionalProperties": false,
61121
+ "$schema": "http://json-schema.org/draft-07/schema#"
61122
+ },
61123
+ "outputJsonSchema": {
61124
+ "type": "object",
61125
+ "properties": {
61126
+ "ok": {
61127
+ "type": "boolean",
61128
+ "enum": [
61129
+ true,
61130
+ false
61131
+ ]
61132
+ },
61133
+ "asset": {
61134
+ "type": "object",
61135
+ "properties": {
61136
+ "assetId": {
61137
+ "type": "string"
61138
+ },
61139
+ "title": {
61140
+ "type": "string"
61141
+ },
61142
+ "description": {
61143
+ "type": [
61144
+ "string",
61145
+ "null"
61146
+ ]
61147
+ },
61148
+ "altText": {
61149
+ "type": [
61150
+ "string",
61151
+ "null"
61152
+ ]
61153
+ },
61154
+ "sourceUrl": {
61155
+ "type": [
61156
+ "string",
61157
+ "null"
61158
+ ]
61159
+ },
61160
+ "sourceKind": {
61161
+ "type": "string"
61162
+ },
61163
+ "mimeType": {
61164
+ "type": "string"
61165
+ },
61166
+ "bytes": {
61167
+ "type": "number"
61168
+ },
61169
+ "width": {
61170
+ "type": "number"
61171
+ },
61172
+ "height": {
61173
+ "type": "number"
61174
+ },
61175
+ "projectId": {
61176
+ "type": [
61177
+ "string",
61178
+ "null"
61179
+ ]
61180
+ },
61181
+ "folderId": {
61182
+ "type": [
61183
+ "string",
61184
+ "null"
61185
+ ]
61186
+ },
61187
+ "tags": {
61188
+ "type": "array",
61189
+ "items": {
61190
+ "type": "string"
61191
+ }
61192
+ },
61193
+ "status": {
61194
+ "type": "string"
61195
+ },
61196
+ "embeddingStatus": {
61197
+ "type": "string"
61198
+ },
61199
+ "createdAt": {
61200
+ "type": "string"
61201
+ },
61202
+ "updatedAt": {
61203
+ "type": "string"
61204
+ },
61205
+ "preview": {
61206
+ "type": "object",
61207
+ "properties": {
61208
+ "url": {
61209
+ "type": "string",
61210
+ "format": "uri"
61211
+ },
61212
+ "expiresAt": {
61213
+ "type": "string",
61214
+ "format": "date-time"
61215
+ },
61216
+ "mimeType": {
61217
+ "type": "string",
61218
+ "const": "image/webp"
61219
+ }
61220
+ },
61221
+ "required": [
61222
+ "url",
61223
+ "expiresAt",
61224
+ "mimeType"
61225
+ ],
61226
+ "additionalProperties": false
61227
+ }
61228
+ },
61229
+ "required": [
61230
+ "assetId",
61231
+ "title",
61232
+ "description",
61233
+ "altText",
61234
+ "sourceUrl",
61235
+ "sourceKind",
61236
+ "mimeType",
61237
+ "bytes",
61238
+ "width",
61239
+ "height",
61240
+ "projectId",
61241
+ "folderId",
61242
+ "tags",
61243
+ "status",
61244
+ "embeddingStatus",
61245
+ "createdAt",
61246
+ "updatedAt"
61247
+ ],
61248
+ "additionalProperties": false
61249
+ },
61250
+ "code": {
61251
+ "type": "string"
61252
+ },
61253
+ "error": {
61254
+ "type": "string"
61255
+ }
61256
+ },
61257
+ "required": [
61258
+ "ok"
61259
+ ],
61260
+ "additionalProperties": false,
61261
+ "$schema": "http://json-schema.org/draft-07/schema#"
61262
+ },
61263
+ "annotations": {
61264
+ "title": "Move Image Asset",
61265
+ "readOnlyHint": false,
61266
+ "destructiveHint": false,
61267
+ "idempotentHint": true,
61268
+ "openWorldHint": false
61269
+ }
61270
+ },
61271
+ {
61272
+ "id": "image_asset_delete",
61273
+ "upstreamName": "image_asset_delete",
61274
+ "description": "DESTRUCTIVE: delete one governed image asset, remove its semantic vectors, and purge private bytes when no other logical asset uses them. Retry with the same assetId if an earlier response was lost.",
61275
+ "inputJsonSchema": {
61276
+ "type": "object",
61277
+ "properties": {
61278
+ "vault": {
61279
+ "type": "string"
61280
+ },
61281
+ "assetId": {
61282
+ "type": "string",
61283
+ "minLength": 1
61284
+ },
61285
+ "confirmDelete": {
61286
+ "type": "boolean",
61287
+ "const": true,
61288
+ "description": "Must be true after the caller has confirmed deletion."
61289
+ }
61290
+ },
61291
+ "required": [
61292
+ "assetId",
61293
+ "confirmDelete"
61294
+ ],
61295
+ "additionalProperties": false,
61296
+ "$schema": "http://json-schema.org/draft-07/schema#"
61297
+ },
61298
+ "outputJsonSchema": {
61299
+ "type": "object",
61300
+ "properties": {
61301
+ "ok": {
61302
+ "type": "boolean",
61303
+ "enum": [
61304
+ true,
61305
+ false
61306
+ ]
61307
+ },
61308
+ "deleted": {
61309
+ "type": "boolean"
61310
+ },
61311
+ "assetId": {
61312
+ "type": "string"
61313
+ },
61314
+ "code": {
61315
+ "type": "string"
61316
+ },
61317
+ "error": {
61318
+ "type": "string"
61319
+ }
61320
+ },
61321
+ "required": [
61322
+ "ok"
61323
+ ],
61324
+ "additionalProperties": false,
61325
+ "$schema": "http://json-schema.org/draft-07/schema#"
61326
+ },
61327
+ "annotations": {
61328
+ "title": "Delete Image Asset",
61329
+ "readOnlyHint": false,
61330
+ "destructiveHint": true,
61331
+ "idempotentHint": true,
61332
+ "openWorldHint": false
61333
+ }
61334
+ }
61335
+ ];
61336
+ }
61337
+ });
61338
+
59588
61339
  // src/mcp/memory-tool-schemas.ts
59589
61340
  var import_zod50, AcceptShareSchema, ApproveSenderSchema, DeclineShareSchema, GetChatLinkSchema, GetVaultAppLinkSchema, InboxSettingsSchema, InviteAccountSchema, issueKeyTool_scopeShape, IssueKeySchema, ListApprovedSendersSchema, ListKeysSchema, NoteInboxSchema, RemoveApprovedSenderSchema, RevokeChatLinkSchema, RevokeKeySchema, RevokeShareSchema, RevokeVaultAppLinkSchema, SetAgentIdentitySchema, SetScopeSchema, ShareNoteSchema, ShareVaultSchema, SwapVaultSchema, SwitchAccountSchema, UnlinkShareSchema, memoryCaptureTool_notePropsSchema, MemoryCaptureSchema, MemoryQuestionsSchema, PrepareMemoryWriteSchema, validateMemoryWriteTool_notePropsSchema, ValidateMemoryWriteSchema, CreateChannelSchema, GetMessageNoteSchema, ListChannelMembersSchema, ListChannelMessagesSchema, MyMentionsSchema, PollChannelSchema, PostMessageSchema, ReactMessageSchema, RemoveChannelMemberSchema, ReplyMessageSchema, factHistoryTool_entryShape, FactHistorySchema, recordFactTool_factShape, RecordFactSchema, NoteBacklinksSchema, GraphPathSchema, GraphUniverseSchema, LibraryIngestSchema, bulkDeleteNotesTool_FilterSchema, BulkDeleteNotesSchema, DeleteNoteSchema, ExportSchema, getTool_notePropsSchema, GetSchema, listNoteSchema, ListSchema, putTool_notePropsSchema, PutSchema, searchTool_primitiveValue, SearchSchema, SuggestSchema, UploadSchema, TemporalRecallSchema, ScheduledArtifactSelectionSchema2, CreateScheduledActionSchema, UpdateScheduledActionSchema, DeleteScheduledActionSchema, GetScheduleLinkSchema, GetScheduleStatusSchema, ListScheduledActionsSchema, PauseScheduledActionSchema, ProposeScheduledActionSchema, ResumeScheduledActionSchema, RevokeScheduleLinkSchema, SetScheduleDefaultsSchema, SetScheduleEntitlementSchema, CostUsageSchema, StorageUsageSchema, CreateTableSchema, deleteTableRowsTool_FilterSchema, DeleteTableRowsSchema, DescribeTableSchema, DropTableSchema, InsertTableRowsSchema, ListTablesSchema, queryTableTool_FilterSchema, QueryTableSchema, ListTagsSchema, MergeTagsSchema, ResolveTagsSchema, UpsertTagSchema, AddVaultSchema, CreateSecureVaultSchema, DeleteVaultSchema, GetVaultContractSchema, ListSharedWithMeSchema, ListVaultsSchema, ProvisionDefaultsSchema, RouteMemorySchema, VideoAnalyzeStartSchema, VideoAnalyzeStatusSchema, CreateWebhookSchema, ListWebhooksSchema, RevokeWebhookSchema, MEMORY_TOOL_SCHEMAS;
59590
61341
  var init_memory_tool_schemas = __esm({
@@ -62114,11 +63865,11 @@ var init_memory_tool_schemas = __esm({
62114
63865
  });
62115
63866
 
62116
63867
  // src/mcp/memory-tool-registry.ts
62117
- var import_image_assets, MEMORY_TOOL_DESCRIPTION_OVERRIDES, MEMORY_TOOL_REGISTRY;
63868
+ var MEMORY_TOOL_DESCRIPTION_OVERRIDES, MEMORY_TOOL_REGISTRY;
62118
63869
  var init_memory_tool_registry = __esm({
62119
63870
  "src/mcp/memory-tool-registry.ts"() {
62120
63871
  "use strict";
62121
- import_image_assets = require("mcpscraper-memory-tools/tools/assets/image-assets");
63872
+ init_memory_image_tool_schemas_generated();
62122
63873
  init_memory_tool_schemas();
62123
63874
  MEMORY_TOOL_DESCRIPTION_OVERRIDES = {
62124
63875
  "access-swap-vault": "Set the account's persisted active vault so later memory calls may omit vault and use it by default across stateless requests. The vault must be entitled to the key and the key must hold 'swap' scope."
@@ -62128,7 +63879,7 @@ var init_memory_tool_registry = __esm({
62128
63879
  ...schema,
62129
63880
  description: MEMORY_TOOL_DESCRIPTION_OVERRIDES[schema.id] ?? schema.description
62130
63881
  })),
62131
- ...import_image_assets.IMAGE_ASSET_MCP_DEFINITIONS
63882
+ ...MEMORY_IMAGE_TOOL_SCHEMAS
62132
63883
  ];
62133
63884
  }
62134
63885
  });
@@ -87416,8 +89167,6 @@ var init_server = __esm({
87416
89167
  if (checked.error) return c.json({ error: checked.error }, 400);
87417
89168
  body.callback_url = checked.parsed?.href;
87418
89169
  }
87419
- const limitErr = await checkHarvestLimits(user, c.req.header("x-mcp-scraper-concurrency-lock"));
87420
- if (limitErr) return c.json(limitErr, 429, { "Retry-After": "30" });
87421
89170
  const options = {
87422
89171
  query: body.query.trim(),
87423
89172
  location: body.location,
@@ -87439,6 +89188,26 @@ var init_server = __esm({
87439
89188
  includeAiOverview: body.includeAiOverview ?? false,
87440
89189
  includeWhatPeopleSaying: body.includeWhatPeopleSaying ?? false
87441
89190
  };
89191
+ const suppliedIdempotencyKey = c.req.header("idempotency-key") ?? c.req.header("x-idempotency-key");
89192
+ let derivedJobId = null;
89193
+ let requestFingerprint3 = null;
89194
+ if (!options.serpOnly && suppliedIdempotencyKey != null) {
89195
+ const trimmedKey = suppliedIdempotencyKey.trim();
89196
+ if (!trimmedKey || trimmedKey.length > 200) {
89197
+ return c.json({ error: "Idempotency-Key must contain 1-200 characters.", errorCode: "invalid_idempotency_key", retryable: false }, 400);
89198
+ }
89199
+ derivedJobId = `paa-${sha256Hex(`${user.id}\0${trimmedKey}`).slice(0, 24)}`;
89200
+ requestFingerprint3 = sha256Hex(JSON.stringify({ options, callbackUrl: body.callback_url ?? null }));
89201
+ const existing = await getJob(derivedJobId, user.id);
89202
+ if (existing) {
89203
+ if (existing.options.requestFingerprint !== requestFingerprint3) {
89204
+ return c.json({ error: "Idempotency-Key was already used for a different asynchronous harvest_paa call.", errorCode: "idempotency_conflict", retryable: false }, 409);
89205
+ }
89206
+ return c.json({ job_id: existing.id, status: existing.status, replayed: true }, ["done", "failed", "cancelled"].includes(existing.status) ? 200 : 202);
89207
+ }
89208
+ }
89209
+ const limitErr = await checkHarvestLimits(user, c.req.header("x-mcp-scraper-concurrency-lock"));
89210
+ if (limitErr) return c.json(limitErr, 429, { "Retry-After": "30" });
87442
89211
  const harvestCost = options.serpOnly ? MC_COSTS.serp_headful : paaCostForQuestionCount2(options.maxQuestions);
87443
89212
  let jobId2;
87444
89213
  if (options.serpOnly) {
@@ -87451,7 +89220,7 @@ var init_server = __esm({
87451
89220
  if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
87452
89221
  jobId2 = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
87453
89222
  } else {
87454
- jobId2 = (0, import_node_crypto72.randomUUID)();
89223
+ jobId2 = derivedJobId ?? (0, import_node_crypto72.randomUUID)();
87455
89224
  const billingDebitKey = `paa-harvest:${jobId2}:hold`;
87456
89225
  const description = `PAA harvest: ${options.query}`.slice(0, 500);
87457
89226
  const hold = await debitMcIdempotent(
@@ -87467,9 +89236,14 @@ var init_server = __esm({
87467
89236
  ...options,
87468
89237
  executionOwner: "inngest",
87469
89238
  billingHoldMc: harvestCost,
87470
- billingDebitKey
89239
+ billingDebitKey,
89240
+ ...requestFingerprint3 ? { requestFingerprint: requestFingerprint3 } : {}
87471
89241
  }, body.callback_url);
87472
89242
  } catch (error) {
89243
+ const existing = derivedJobId ? await getJob(derivedJobId, user.id).catch(() => null) : null;
89244
+ if (existing && existing.options.requestFingerprint === requestFingerprint3) {
89245
+ return c.json({ job_id: existing.id, status: existing.status, replayed: true }, 202);
89246
+ }
87473
89247
  await settleDebitMcIdempotent(
87474
89248
  user.id,
87475
89249
  billingDebitKey,
@@ -89282,4 +91056,3 @@ async function main() {
89282
91056
  }
89283
91057
  }
89284
91058
  void main();
89285
- //# sourceMappingURL=api-server.cjs.map